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 }