github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-fuzzer/proc.go (about)

     1  // Copyright 2017 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"math/rand"
    10  	"os"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	"github.com/google/syzkaller/pkg/flatrpc"
    15  	"github.com/google/syzkaller/pkg/ipc"
    16  	"github.com/google/syzkaller/pkg/log"
    17  	"github.com/google/syzkaller/pkg/osutil"
    18  	"github.com/google/syzkaller/pkg/rpctype"
    19  )
    20  
    21  // Proc represents a single fuzzing process (executor).
    22  type Proc struct {
    23  	tool *FuzzerTool
    24  	pid  int
    25  	env  *ipc.Env
    26  }
    27  
    28  func startProc(tool *FuzzerTool, pid int, config *ipc.Config) {
    29  	env, err := ipc.MakeEnv(config, pid)
    30  	if err != nil {
    31  		log.SyzFatalf("failed to create env: %v", err)
    32  	}
    33  	proc := &Proc{
    34  		tool: tool,
    35  		pid:  pid,
    36  		env:  env,
    37  	}
    38  	go proc.loop()
    39  }
    40  
    41  func (proc *Proc) loop() {
    42  	rnd := rand.New(rand.NewSource(time.Now().UnixNano() + int64(proc.pid)))
    43  	for {
    44  		req := proc.nextRequest()
    45  		// Do not let too much state accumulate.
    46  		const restartIn = 600
    47  		resetFlags := flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover | flatrpc.ExecFlagCollectComps
    48  		if (req.ExecOpts.ExecFlags&resetFlags != 0 &&
    49  			rnd.Intn(restartIn) == 0) || req.ResetState {
    50  			proc.env.ForceRestart()
    51  		}
    52  		info, output, err, try := proc.execute(req)
    53  		res := executionResult{
    54  			ExecutionRequest: req,
    55  			procID:           proc.pid,
    56  			try:              try,
    57  			info:             info,
    58  			output:           output,
    59  			err:              err,
    60  		}
    61  		for i := 1; i < req.Repeat && res.err == "" && !req.IsBinary; i++ {
    62  			// Recreate Env every few iterations, this allows to cover more paths.
    63  			if i%2 == 0 {
    64  				proc.env.ForceRestart()
    65  			}
    66  			info, output, err, _ := proc.execute(req)
    67  			if res.info == nil {
    68  				res.info = info
    69  			} else {
    70  				res.info.Calls = append(res.info.Calls, info.Calls...)
    71  			}
    72  			res.output = append(res.output, output...)
    73  			res.err = err
    74  		}
    75  		// Let's perform signal filtering in a separate thread to get the most
    76  		// exec/sec out of a syz-executor instance.
    77  		proc.tool.results <- res
    78  	}
    79  }
    80  
    81  func (proc *Proc) nextRequest() rpctype.ExecutionRequest {
    82  	select {
    83  	case req := <-proc.tool.requests:
    84  		return req
    85  	default:
    86  	}
    87  	// Not having enough inputs to execute is a sign of RPC communication problems.
    88  	// Let's count and report such situations.
    89  	start := osutil.MonotonicNano()
    90  	req := <-proc.tool.requests
    91  	proc.tool.noExecDuration.Add(uint64(osutil.MonotonicNano() - start))
    92  	proc.tool.noExecRequests.Add(1)
    93  	return req
    94  }
    95  
    96  func (proc *Proc) execute(req rpctype.ExecutionRequest) (info *ipc.ProgInfo, output []byte, errStr string, try int) {
    97  	var err error
    98  	if req.IsBinary {
    99  		output, err = executeBinary(req)
   100  	} else {
   101  		info, output, try, err = proc.executeProgram(req)
   102  	}
   103  	if !req.ReturnOutput {
   104  		output = nil
   105  	}
   106  	if err != nil {
   107  		errStr = err.Error()
   108  	}
   109  	return
   110  }
   111  
   112  func (proc *Proc) executeProgram(req rpctype.ExecutionRequest) (*ipc.ProgInfo, []byte, int, error) {
   113  	for try := 0; ; try++ {
   114  		var output []byte
   115  		var info *ipc.ProgInfo
   116  		var hanged bool
   117  		// On a heavily loaded VM, syz-executor may take significant time to start.
   118  		// Let's do it outside of the gate ticket.
   119  		err := proc.env.RestartIfNeeded(&req.ExecOpts)
   120  		if err == nil {
   121  			// Limit concurrency.
   122  			ticket := proc.tool.gate.Enter()
   123  			proc.tool.startExecutingCall(req.ID, proc.pid, try)
   124  			output, info, hanged, err = proc.env.ExecProg(&req.ExecOpts, req.ProgData)
   125  			proc.tool.gate.Leave(ticket)
   126  			// Don't print output if returning error b/c it may contain SYZFAIL.
   127  			if !req.ReturnError {
   128  				log.Logf(2, "result hanged=%v err=%v: %s", hanged, err, output)
   129  			}
   130  			if hanged && err == nil && req.ReturnError {
   131  				err = errors.New("hanged")
   132  			}
   133  		}
   134  		if err == nil || req.ReturnError {
   135  			return info, output, try, err
   136  		}
   137  		log.Logf(4, "fuzzer detected executor failure='%v', retrying #%d", err, try+1)
   138  		if try > 10 {
   139  			log.SyzFatalf("executor %v failed %v times: %v\n%s", proc.pid, try, err, output)
   140  		} else if try > 3 {
   141  			time.Sleep(100 * time.Millisecond)
   142  		}
   143  	}
   144  }
   145  
   146  func executeBinary(req rpctype.ExecutionRequest) ([]byte, error) {
   147  	tmp, err := os.MkdirTemp("", "syz-runtest")
   148  	if err != nil {
   149  		return nil, fmt.Errorf("failed to create temp dir: %w", err)
   150  	}
   151  	defer os.RemoveAll(tmp)
   152  	bin := filepath.Join(tmp, "syz-executor")
   153  	if err := os.WriteFile(bin, req.ProgData, 0777); err != nil {
   154  		return nil, fmt.Errorf("failed to write binary: %w", err)
   155  	}
   156  	cmd := osutil.Command(bin)
   157  	cmd.Dir = tmp
   158  	// Tell ASAN to not mess with our NONFAILING.
   159  	cmd.Env = append(append([]string{}, os.Environ()...), "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1")
   160  	output, err := osutil.Run(20*time.Second, cmd)
   161  	var verr *osutil.VerboseError
   162  	if errors.As(err, &verr) {
   163  		// The process can legitimately do something like exit_group(1).
   164  		// So we ignore the error and rely on the rest of the checks (e.g. syscall return values).
   165  		return verr.Output, nil
   166  	}
   167  	return output, err
   168  }