github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-fuzzer/fuzzer.go (about) 1 // Copyright 2015 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 "flag" 8 "fmt" 9 "net/http" 10 _ "net/http/pprof" 11 "os" 12 "path/filepath" 13 "runtime" 14 "runtime/debug" 15 "sync" 16 "sync/atomic" 17 "time" 18 19 "github.com/google/syzkaller/pkg/flatrpc" 20 "github.com/google/syzkaller/pkg/host" 21 "github.com/google/syzkaller/pkg/ipc" 22 "github.com/google/syzkaller/pkg/ipc/ipcconfig" 23 "github.com/google/syzkaller/pkg/log" 24 "github.com/google/syzkaller/pkg/osutil" 25 "github.com/google/syzkaller/pkg/rpctype" 26 "github.com/google/syzkaller/pkg/signal" 27 "github.com/google/syzkaller/pkg/tool" 28 "github.com/google/syzkaller/prog" 29 _ "github.com/google/syzkaller/sys" 30 "github.com/google/syzkaller/sys/targets" 31 ) 32 33 type FuzzerTool struct { 34 name string 35 executor string 36 gate *ipc.Gate 37 manager *rpctype.RPCClient 38 // TODO: repair triagedCandidates logic, it's broken now. 39 triagedCandidates uint32 40 timeouts targets.Timeouts 41 leakFrames []string 42 43 noExecRequests atomic.Uint64 44 noExecDuration atomic.Uint64 45 46 requests chan rpctype.ExecutionRequest 47 results chan executionResult 48 signalMu sync.RWMutex 49 maxSignal signal.Signal 50 } 51 52 // executionResult offloads some computations from the proc loop 53 // to the communication thread. 54 type executionResult struct { 55 rpctype.ExecutionRequest 56 procID int 57 try int 58 info *ipc.ProgInfo 59 output []byte 60 err string 61 } 62 63 // Gate size controls how deep in the log the last executed by every proc 64 // program may be. The intent is to make sure that, given the output log, 65 // we always understand what was happening. 66 // Judging by the logs collected on syzbot, 32 should be a reasonable figure. 67 // It coincides with prog.MaxPids. 68 const gateSize = prog.MaxPids 69 70 // TODO: split into smaller methods. 71 // nolint: funlen, gocyclo 72 func main() { 73 debug.SetGCPercent(50) 74 75 var ( 76 flagName = flag.String("name", "test", "unique name for manager") 77 flagOS = flag.String("os", runtime.GOOS, "target OS") 78 flagArch = flag.String("arch", runtime.GOARCH, "target arch") 79 flagManager = flag.String("manager", "", "manager rpc address") 80 flagProcs = flag.Int("procs", 1, "number of parallel test processes") 81 flagTest = flag.Bool("test", false, "enable image testing mode") // used by syz-ci 82 flagPprofPort = flag.Int("pprof_port", 0, "HTTP port for the pprof endpoint (disabled if 0)") 83 ) 84 defer tool.Init()() 85 log.Logf(0, "fuzzer started") 86 87 target, err := prog.GetTarget(*flagOS, *flagArch) 88 if err != nil { 89 log.SyzFatal(err) 90 } 91 92 config, execOpts, err := ipcconfig.Default(target) 93 if err != nil { 94 log.SyzFatalf("failed to create default ipc config: %v", err) 95 } 96 timeouts := config.Timeouts 97 executor := config.Executor 98 shutdown := make(chan struct{}) 99 osutil.HandleInterrupts(shutdown) 100 go func() { 101 // Handles graceful preemption on GCE. 102 <-shutdown 103 log.Logf(0, "SYZ-FUZZER: PREEMPTED") 104 os.Exit(1) 105 }() 106 107 if *flagPprofPort != 0 { 108 setupPprofHandler(*flagPprofPort) 109 } 110 111 if *flagTest { 112 checkArgs := &checkArgs{ 113 target: target, 114 sandbox: ipc.FlagsToSandbox(execOpts.EnvFlags), 115 ipcConfig: config, 116 ipcExecOpts: execOpts, 117 gitRevision: prog.GitRevision, 118 targetRevision: target.Revision, 119 } 120 testImage(*flagManager, checkArgs) 121 return 122 } 123 124 log.Logf(0, "dialing manager at %v", *flagManager) 125 manager, err := rpctype.NewRPCClient(*flagManager) 126 if err != nil { 127 log.SyzFatalf("failed to create an RPC client: %v ", err) 128 } 129 130 log.Logf(1, "connecting to manager...") 131 a := &rpctype.ConnectArgs{ 132 Name: *flagName, 133 GitRevision: prog.GitRevision, 134 SyzRevision: target.Revision, 135 } 136 a.ExecutorArch, a.ExecutorSyzRevision, a.ExecutorGitRevision, err = executorVersion(executor) 137 if err != nil { 138 log.SyzFatalf("failed to run executor version: %v ", err) 139 } 140 r := &rpctype.ConnectRes{} 141 if err := manager.Call("Manager.Connect", a, r); err != nil { 142 log.SyzFatalf("failed to call Manager.Connect(): %v ", err) 143 } 144 checkReq := &rpctype.CheckArgs{ 145 Name: *flagName, 146 Files: host.ReadFiles(r.ReadFiles), 147 Globs: make(map[string][]string), 148 } 149 features, err := host.SetupFeatures(target, executor, r.Features, nil) 150 if err != nil { 151 log.SyzFatalf("failed to setup features: %v ", err) 152 } 153 checkReq.Features = features 154 for _, glob := range r.ReadGlobs { 155 files, err := filepath.Glob(filepath.FromSlash(glob)) 156 if err != nil && checkReq.Error == "" { 157 checkReq.Error = fmt.Sprintf("failed to read glob %q: %v", glob, err) 158 } 159 checkReq.Globs[glob] = files 160 } 161 checkRes := new(rpctype.CheckRes) 162 if err := manager.Call("Manager.Check", checkReq, checkRes); err != nil { 163 log.SyzFatalf("Manager.Check call failed: %v", err) 164 } 165 if checkReq.Error != "" { 166 log.SyzFatalf("%v", checkReq.Error) 167 } 168 169 if checkRes.CoverFilterBitmap != nil { 170 if err := osutil.WriteFile("syz-cover-bitmap", checkRes.CoverFilterBitmap); err != nil { 171 log.SyzFatalf("failed to write syz-cover-bitmap: %v", err) 172 } 173 } 174 175 inputsCount := *flagProcs * 2 176 fuzzerTool := &FuzzerTool{ 177 name: *flagName, 178 executor: executor, 179 manager: manager, 180 timeouts: timeouts, 181 leakFrames: r.MemoryLeakFrames, 182 183 requests: make(chan rpctype.ExecutionRequest, 2*inputsCount), 184 results: make(chan executionResult, 2*inputsCount), 185 } 186 fuzzerTool.filterDataRaceFrames(r.DataRaceFrames) 187 var gateCallback func() 188 for _, feat := range features { 189 if feat.Id == flatrpc.FeatureLeak && feat.Reason == "" { 190 gateCallback = fuzzerTool.leakGateCallback 191 } 192 } 193 fuzzerTool.gate = ipc.NewGate(gateSize, gateCallback) 194 195 log.Logf(0, "starting %v executor processes", *flagProcs) 196 for pid := 0; pid < *flagProcs; pid++ { 197 startProc(fuzzerTool, pid, config) 198 } 199 200 // Query enough inputs at the beginning. 201 fuzzerTool.exchangeDataCall(nil, 0) 202 go fuzzerTool.exchangeDataWorker() 203 fuzzerTool.exchangeDataWorker() 204 } 205 206 func (tool *FuzzerTool) leakGateCallback() { 207 // Leak checking is very slow so we don't do it while triaging the corpus 208 // (otherwise it takes infinity). When we have presumably triaged the corpus 209 // (triagedCandidates == 1), we run leak checking bug ignore the result 210 // to flush any previous leaks. After that (triagedCandidates == 2) 211 // we do actual leak checking and report leaks. 212 triagedCandidates := atomic.LoadUint32(&tool.triagedCandidates) 213 if triagedCandidates == 0 { 214 return 215 } 216 args := append([]string{"leak"}, tool.leakFrames...) 217 timeout := tool.timeouts.NoOutput * 9 / 10 218 output, err := osutil.RunCmd(timeout, "", tool.executor, args...) 219 if err != nil && triagedCandidates == 2 { 220 // If we exit right away, dying executors will dump lots of garbage to console. 221 os.Stdout.Write(output) 222 fmt.Printf("BUG: leak checking failed\n") 223 time.Sleep(time.Hour) 224 os.Exit(1) 225 } 226 if triagedCandidates == 1 { 227 atomic.StoreUint32(&tool.triagedCandidates, 2) 228 } 229 } 230 231 func (tool *FuzzerTool) filterDataRaceFrames(frames []string) { 232 if len(frames) == 0 { 233 return 234 } 235 args := append([]string{"setup_kcsan_filterlist"}, frames...) 236 timeout := time.Minute * tool.timeouts.Scale 237 output, err := osutil.RunCmd(timeout, "", tool.executor, args...) 238 if err != nil { 239 log.SyzFatalf("failed to set KCSAN filterlist: %v", err) 240 } 241 log.Logf(0, "%s", output) 242 } 243 244 func (tool *FuzzerTool) startExecutingCall(progID int64, pid, try int) { 245 tool.manager.AsyncCall("Manager.StartExecuting", &rpctype.ExecutingRequest{ 246 Name: tool.name, 247 ID: progID, 248 ProcID: pid, 249 Try: try, 250 }) 251 } 252 253 func (tool *FuzzerTool) exchangeDataCall(results []rpctype.ExecutionResult, latency time.Duration) time.Duration { 254 needProgs := max(0, cap(tool.requests)/2-len(tool.requests)) 255 a := &rpctype.ExchangeInfoRequest{ 256 Name: tool.name, 257 NeedProgs: needProgs, 258 Results: results, 259 StatsDelta: tool.grabStats(), 260 Latency: latency, 261 } 262 r := &rpctype.ExchangeInfoReply{} 263 start := osutil.MonotonicNano() 264 if err := tool.manager.Call("Manager.ExchangeInfo", a, r); err != nil { 265 log.SyzFatalf("Manager.ExchangeInfo call failed: %v", err) 266 } 267 latency = osutil.MonotonicNano() - start 268 tool.updateMaxSignal(r.NewMaxSignal, r.DropMaxSignal) 269 if len(r.Requests) == 0 { 270 // This is possible during initial checking stage, backoff a bit. 271 time.Sleep(100 * time.Millisecond) 272 } 273 for _, req := range r.Requests { 274 tool.requests <- req 275 } 276 return latency 277 } 278 279 func (tool *FuzzerTool) exchangeDataWorker() { 280 var latency time.Duration 281 ticker := time.NewTicker(3 * time.Second * tool.timeouts.Scale).C 282 for { 283 var results []rpctype.ExecutionResult 284 select { 285 case res := <-tool.results: 286 results = append(results, tool.convertExecutionResult(res)) 287 case <-ticker: 288 // This is not expected to happen a lot, 289 // but this is required to resolve potential deadlock 290 // during initial checking stage when we may get 291 // no test requests from the host in some requests. 292 } 293 // Grab other finished calls, just in case there are any. 294 loop: 295 for { 296 select { 297 case res := <-tool.results: 298 results = append(results, tool.convertExecutionResult(res)) 299 default: 300 break loop 301 } 302 } 303 // Replenish exactly the finished requests. 304 latency = tool.exchangeDataCall(results, latency) 305 } 306 } 307 308 func (tool *FuzzerTool) convertExecutionResult(res executionResult) rpctype.ExecutionResult { 309 ret := rpctype.ExecutionResult{ 310 ID: res.ID, 311 ProcID: res.procID, 312 Try: res.try, 313 Output: res.output, 314 Error: res.err, 315 } 316 if res.info != nil { 317 if res.NewSignal { 318 tool.diffMaxSignal(res.info, res.SignalFilter, res.SignalFilterCall) 319 } 320 ret.Info = *res.info 321 } 322 return ret 323 } 324 325 func (tool *FuzzerTool) grabStats() map[string]uint64 { 326 return map[string]uint64{ 327 "no exec requests": tool.noExecRequests.Swap(0), 328 "no exec duration": tool.noExecDuration.Swap(0), 329 } 330 } 331 332 func (tool *FuzzerTool) diffMaxSignal(info *ipc.ProgInfo, mask signal.Signal, maskCall int) { 333 tool.signalMu.RLock() 334 defer tool.signalMu.RUnlock() 335 diffMaxSignal(info, tool.maxSignal, mask, maskCall) 336 } 337 338 func diffMaxSignal(info *ipc.ProgInfo, max, mask signal.Signal, maskCall int) { 339 info.Extra.Signal = diffCallSignal(info.Extra.Signal, max, mask, -1, maskCall) 340 for i := 0; i < len(info.Calls); i++ { 341 info.Calls[i].Signal = diffCallSignal(info.Calls[i].Signal, max, mask, i, maskCall) 342 } 343 } 344 345 func diffCallSignal(raw []uint32, max, mask signal.Signal, call, maskCall int) []uint32 { 346 if mask != nil && call == maskCall { 347 return signal.FilterRaw(raw, max, mask) 348 } 349 return max.DiffFromRaw(raw) 350 } 351 352 func (tool *FuzzerTool) updateMaxSignal(add, drop []uint32) { 353 tool.signalMu.Lock() 354 defer tool.signalMu.Unlock() 355 tool.maxSignal.Subtract(signal.FromRaw(drop, 0)) 356 tool.maxSignal.Merge(signal.FromRaw(add, 0)) 357 } 358 359 func setupPprofHandler(port int) { 360 // Necessary for pprof handlers. 361 go func() { 362 err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", port), nil) 363 if err != nil { 364 log.SyzFatalf("failed to setup a server: %v", err) 365 } 366 }() 367 }