github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/fuzzer/fuzzer.go (about) 1 // Copyright 2024 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 fuzzer 5 6 import ( 7 "context" 8 "fmt" 9 "math/rand" 10 "runtime" 11 "sort" 12 "sync" 13 "time" 14 15 "github.com/google/syzkaller/pkg/corpus" 16 "github.com/google/syzkaller/pkg/csource" 17 "github.com/google/syzkaller/pkg/flatrpc" 18 "github.com/google/syzkaller/pkg/fuzzer/queue" 19 "github.com/google/syzkaller/pkg/mgrconfig" 20 "github.com/google/syzkaller/pkg/signal" 21 "github.com/google/syzkaller/pkg/stat" 22 "github.com/google/syzkaller/prog" 23 ) 24 25 type Fuzzer struct { 26 Stats 27 Config *Config 28 Cover *Cover 29 30 ctx context.Context 31 mu sync.Mutex 32 rnd *rand.Rand 33 target *prog.Target 34 hintsLimiter prog.HintsLimiter 35 runningJobs map[jobIntrospector]struct{} 36 37 ct *prog.ChoiceTable 38 ctProgs int 39 ctMu sync.Mutex // TODO: use RWLock. 40 ctRegenerate chan struct{} 41 42 execQueues 43 } 44 45 func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand, 46 target *prog.Target) *Fuzzer { 47 if cfg.NewInputFilter == nil { 48 cfg.NewInputFilter = func(call string) bool { 49 return true 50 } 51 } 52 f := &Fuzzer{ 53 Stats: newStats(target), 54 Config: cfg, 55 Cover: newCover(), 56 57 ctx: ctx, 58 rnd: rnd, 59 target: target, 60 runningJobs: map[jobIntrospector]struct{}{}, 61 62 // We're okay to lose some of the messages -- if we are already 63 // regenerating the table, we don't want to repeat it right away. 64 ctRegenerate: make(chan struct{}), 65 } 66 f.execQueues = newExecQueues(f) 67 f.updateChoiceTable(nil) 68 go f.choiceTableUpdater() 69 if cfg.Debug { 70 go f.logCurrentStats() 71 } 72 return f 73 } 74 75 func (fuzzer *Fuzzer) RecommendedCalls() int { 76 if fuzzer.Config.ModeKFuzzTest { 77 return prog.RecommendedCallsKFuzzTest 78 } 79 return prog.RecommendedCalls 80 } 81 82 type execQueues struct { 83 triageCandidateQueue *queue.DynamicOrderer 84 candidateQueue *queue.PlainQueue 85 triageQueue *queue.DynamicOrderer 86 smashQueue *queue.PlainQueue 87 source queue.Source 88 } 89 90 func newExecQueues(fuzzer *Fuzzer) execQueues { 91 ret := execQueues{ 92 triageCandidateQueue: queue.DynamicOrder(), 93 candidateQueue: queue.Plain(), 94 triageQueue: queue.DynamicOrder(), 95 smashQueue: queue.Plain(), 96 } 97 // Alternate smash jobs with exec/fuzz to spread attention to the wider area. 98 skipQueue := 3 99 if fuzzer.Config.PatchTest { 100 // When we do patch fuzzing, we do not focus on finding and persisting 101 // new coverage that much, so it's reasonable to spend more time just 102 // mutating various corpus programs. 103 skipQueue = 2 104 } 105 // Sources are listed in the order, in which they will be polled. 106 ret.source = queue.Order( 107 ret.triageCandidateQueue, 108 ret.candidateQueue, 109 ret.triageQueue, 110 queue.Alternate(ret.smashQueue, skipQueue), 111 queue.Callback(fuzzer.genFuzz), 112 ) 113 return ret 114 } 115 116 func (fuzzer *Fuzzer) CandidatesToTriage() int { 117 return fuzzer.statCandidates.Val() + fuzzer.statJobsTriageCandidate.Val() 118 } 119 120 func (fuzzer *Fuzzer) CandidateTriageFinished() bool { 121 return fuzzer.CandidatesToTriage() == 0 122 } 123 124 func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request) *queue.Result { 125 return fuzzer.executeWithFlags(executor, req, 0) 126 } 127 128 func (fuzzer *Fuzzer) executeWithFlags(executor queue.Executor, req *queue.Request, flags ProgFlags) *queue.Result { 129 fuzzer.enqueue(executor, req, flags, 0) 130 return req.Wait(fuzzer.ctx) 131 } 132 133 func (fuzzer *Fuzzer) prepare(req *queue.Request, flags ProgFlags, attempt int) { 134 req.OnDone(func(req *queue.Request, res *queue.Result) bool { 135 return fuzzer.processResult(req, res, flags, attempt) 136 }) 137 } 138 139 func (fuzzer *Fuzzer) enqueue(executor queue.Executor, req *queue.Request, flags ProgFlags, attempt int) { 140 fuzzer.prepare(req, flags, attempt) 141 executor.Submit(req) 142 } 143 144 func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags ProgFlags, attempt int) bool { 145 // If we are already triaging this exact prog, this is flaky coverage. 146 // Hanged programs are harmful as they consume executor procs. 147 dontTriage := flags&progInTriage > 0 || res.Status == queue.Hanged 148 // Triage the program. 149 // We do it before unblocking the waiting threads because 150 // it may result it concurrent modification of req.Prog. 151 var triage map[int]*triageCall 152 if req.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectSignal > 0 && res.Info != nil && !dontTriage { 153 for call, info := range res.Info.Calls { 154 fuzzer.triageProgCall(req.Prog, info, call, &triage) 155 } 156 fuzzer.triageProgCall(req.Prog, res.Info.Extra, -1, &triage) 157 158 if len(triage) != 0 { 159 queue, stat := fuzzer.triageQueue, fuzzer.statJobsTriage 160 if flags&progCandidate > 0 { 161 queue, stat = fuzzer.triageCandidateQueue, fuzzer.statJobsTriageCandidate 162 } 163 job := &triageJob{ 164 p: req.Prog.Clone(), 165 executor: res.Executor, 166 flags: flags, 167 queue: queue.Append(), 168 calls: triage, 169 info: &JobInfo{ 170 Name: req.Prog.String(), 171 Type: "triage", 172 }, 173 } 174 for id := range triage { 175 job.info.Calls = append(job.info.Calls, job.p.CallName(id)) 176 } 177 sort.Strings(job.info.Calls) 178 fuzzer.startJob(stat, job) 179 } 180 } 181 182 if res.Info != nil { 183 fuzzer.statExecTime.Add(int(res.Info.Elapsed / 1e6)) 184 for call, info := range res.Info.Calls { 185 fuzzer.handleCallInfo(req, info, call) 186 } 187 fuzzer.handleCallInfo(req, res.Info.Extra, -1) 188 } 189 190 // Corpus candidates may have flaky coverage, so we give them a second chance. 191 maxCandidateAttempts := 3 192 if req.Risky() { 193 // In non-snapshot mode usually we are not sure which exactly input caused the crash, 194 // so give it one more chance. In snapshot mode we know for sure, so don't retry. 195 maxCandidateAttempts = 2 196 if fuzzer.Config.Snapshot || res.Status == queue.Hanged { 197 maxCandidateAttempts = 0 198 } 199 } 200 if len(triage) == 0 && flags&ProgFromCorpus != 0 && attempt < maxCandidateAttempts { 201 fuzzer.enqueue(fuzzer.candidateQueue, req, flags, attempt+1) 202 return false 203 } 204 if flags&progCandidate != 0 { 205 fuzzer.statCandidates.Add(-1) 206 } 207 return true 208 } 209 210 type Config struct { 211 Debug bool 212 Corpus *corpus.Corpus 213 Logf func(level int, msg string, args ...interface{}) 214 Snapshot bool 215 Coverage bool 216 FaultInjection bool 217 Comparisons bool 218 Collide bool 219 EnabledCalls map[*prog.Syscall]bool 220 NoMutateCalls map[int]bool 221 FetchRawCover bool 222 NewInputFilter func(call string) bool 223 PatchTest bool 224 ModeKFuzzTest bool 225 } 226 227 func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call int, triage *map[int]*triageCall) { 228 if info == nil { 229 return 230 } 231 prio := signalPrio(p, info, call) 232 newMaxSignal := fuzzer.Cover.addRawMaxSignal(info.Signal, prio) 233 if newMaxSignal.Empty() { 234 return 235 } 236 if !fuzzer.Config.NewInputFilter(p.CallName(call)) { 237 return 238 } 239 fuzzer.Logf(2, "found new signal in call %d in %s", call, p) 240 if *triage == nil { 241 *triage = make(map[int]*triageCall) 242 } 243 (*triage)[call] = &triageCall{ 244 errno: info.Error, 245 newSignal: newMaxSignal, 246 signals: [deflakeNeedRuns]signal.Signal{signal.FromRaw(info.Signal, prio)}, 247 } 248 } 249 250 func (fuzzer *Fuzzer) handleCallInfo(req *queue.Request, info *flatrpc.CallInfo, call int) { 251 if info == nil || info.Flags&flatrpc.CallFlagCoverageOverflow == 0 { 252 return 253 } 254 syscallIdx := len(fuzzer.Syscalls) - 1 255 if call != -1 { 256 syscallIdx = req.Prog.Calls[call].Meta.ID 257 } 258 stat := &fuzzer.Syscalls[syscallIdx] 259 if req.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectComps != 0 { 260 stat.CompsOverflows.Add(1) 261 fuzzer.statCompsOverflows.Add(1) 262 } else { 263 stat.CoverOverflows.Add(1) 264 fuzzer.statCoverOverflows.Add(1) 265 } 266 } 267 268 func signalPrio(p *prog.Prog, info *flatrpc.CallInfo, call int) (prio uint8) { 269 if call == -1 { 270 return 0 271 } 272 if info.Error == 0 { 273 prio |= 1 << 1 274 } 275 if !p.Target.CallContainsAny(p.Calls[call]) { 276 prio |= 1 << 0 277 } 278 return 279 } 280 281 func (fuzzer *Fuzzer) genFuzz() *queue.Request { 282 // Either generate a new input or mutate an existing one. 283 mutateRate := 0.95 284 if !fuzzer.Config.Coverage { 285 // If we don't have real coverage signal, generate programs 286 // more frequently because fallback signal is weak. 287 mutateRate = 0.5 288 } 289 var req *queue.Request 290 rnd := fuzzer.rand() 291 if rnd.Float64() < mutateRate { 292 req = mutateProgRequest(fuzzer, rnd) 293 } 294 if req == nil { 295 req = genProgRequest(fuzzer, rnd) 296 } 297 if fuzzer.Config.Collide && rnd.Intn(3) == 0 { 298 req = &queue.Request{ 299 Prog: randomCollide(req.Prog, rnd), 300 Stat: fuzzer.statExecCollide, 301 } 302 } 303 fuzzer.prepare(req, 0, 0) 304 return req 305 } 306 307 func (fuzzer *Fuzzer) startJob(stat *stat.Val, newJob job) { 308 fuzzer.Logf(2, "started %T", newJob) 309 go func() { 310 stat.Add(1) 311 defer stat.Add(-1) 312 313 fuzzer.statJobs.Add(1) 314 defer fuzzer.statJobs.Add(-1) 315 316 if obj, ok := newJob.(jobIntrospector); ok { 317 fuzzer.mu.Lock() 318 fuzzer.runningJobs[obj] = struct{}{} 319 fuzzer.mu.Unlock() 320 321 defer func() { 322 fuzzer.mu.Lock() 323 delete(fuzzer.runningJobs, obj) 324 fuzzer.mu.Unlock() 325 }() 326 } 327 328 newJob.run(fuzzer) 329 }() 330 } 331 332 func (fuzzer *Fuzzer) Next() *queue.Request { 333 req := fuzzer.source.Next() 334 if req == nil { 335 // The fuzzer is not supposed to issue nil requests. 336 panic("nil request from the fuzzer") 337 } 338 return req 339 } 340 341 func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) { 342 if fuzzer.Config.Logf == nil { 343 return 344 } 345 fuzzer.Config.Logf(level, msg, args...) 346 } 347 348 type ProgFlags int 349 350 const ( 351 // The candidate was loaded from our local corpus rather than come from hub. 352 ProgFromCorpus ProgFlags = 1 << iota 353 ProgMinimized 354 ProgSmashed 355 356 progCandidate 357 progInTriage 358 ) 359 360 type Candidate struct { 361 Prog *prog.Prog 362 Flags ProgFlags 363 } 364 365 func (fuzzer *Fuzzer) AddCandidates(candidates []Candidate) { 366 fuzzer.statCandidates.Add(len(candidates)) 367 for _, candidate := range candidates { 368 req := &queue.Request{ 369 Prog: candidate.Prog, 370 ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), 371 Stat: fuzzer.statExecCandidate, 372 Important: true, 373 } 374 fuzzer.enqueue(fuzzer.candidateQueue, req, candidate.Flags|progCandidate, 0) 375 } 376 } 377 378 func (fuzzer *Fuzzer) rand() *rand.Rand { 379 fuzzer.mu.Lock() 380 defer fuzzer.mu.Unlock() 381 return rand.New(rand.NewSource(fuzzer.rnd.Int63())) 382 } 383 384 func (fuzzer *Fuzzer) updateChoiceTable(programs []*prog.Prog) { 385 newCt := fuzzer.target.BuildChoiceTable(programs, fuzzer.Config.EnabledCalls) 386 387 fuzzer.ctMu.Lock() 388 defer fuzzer.ctMu.Unlock() 389 if len(programs) >= fuzzer.ctProgs { 390 fuzzer.ctProgs = len(programs) 391 fuzzer.ct = newCt 392 } 393 } 394 395 func (fuzzer *Fuzzer) choiceTableUpdater() { 396 for { 397 select { 398 case <-fuzzer.ctx.Done(): 399 return 400 case <-fuzzer.ctRegenerate: 401 } 402 fuzzer.updateChoiceTable(fuzzer.Config.Corpus.Programs()) 403 } 404 } 405 406 func (fuzzer *Fuzzer) ChoiceTable() *prog.ChoiceTable { 407 progs := fuzzer.Config.Corpus.Programs() 408 409 fuzzer.ctMu.Lock() 410 defer fuzzer.ctMu.Unlock() 411 412 // There were no deep ideas nor any calculations behind these numbers. 413 regenerateEveryProgs := 333 414 if len(progs) < 100 { 415 regenerateEveryProgs = 33 416 } 417 if fuzzer.ctProgs+regenerateEveryProgs < len(progs) { 418 select { 419 case fuzzer.ctRegenerate <- struct{}{}: 420 default: 421 // We're okay to lose the message. 422 // It means that we're already regenerating the table. 423 } 424 } 425 return fuzzer.ct 426 } 427 428 func (fuzzer *Fuzzer) RunningJobs() []*JobInfo { 429 fuzzer.mu.Lock() 430 defer fuzzer.mu.Unlock() 431 432 var ret []*JobInfo 433 for item := range fuzzer.runningJobs { 434 ret = append(ret, item.getInfo()) 435 } 436 return ret 437 } 438 439 func (fuzzer *Fuzzer) logCurrentStats() { 440 for { 441 select { 442 case <-time.After(time.Minute): 443 case <-fuzzer.ctx.Done(): 444 return 445 } 446 447 var m runtime.MemStats 448 runtime.ReadMemStats(&m) 449 450 str := fmt.Sprintf("running jobs: %d, heap (MB): %d", 451 fuzzer.statJobs.Val(), m.Alloc/1000/1000) 452 fuzzer.Logf(0, "%s", str) 453 } 454 } 455 456 func setFlags(execFlags flatrpc.ExecFlag) flatrpc.ExecOpts { 457 return flatrpc.ExecOpts{ 458 ExecFlags: execFlags, 459 } 460 } 461 462 // TODO: This method belongs better to pkg/flatrpc, but we currently end up 463 // having a cyclic dependency error. 464 func DefaultExecOpts(cfg *mgrconfig.Config, features flatrpc.Feature, debug bool) flatrpc.ExecOpts { 465 env := csource.FeaturesToFlags(features, nil) 466 if debug { 467 env |= flatrpc.ExecEnvDebug 468 } 469 if cfg.Experimental.ResetAccState { 470 env |= flatrpc.ExecEnvResetState 471 } 472 if cfg.Cover { 473 env |= flatrpc.ExecEnvSignal 474 } 475 sandbox, err := flatrpc.SandboxToFlags(cfg.Sandbox) 476 if err != nil { 477 panic(fmt.Sprintf("failed to parse sandbox: %v", err)) 478 } 479 env |= sandbox 480 481 exec := flatrpc.ExecFlagThreaded 482 if !cfg.RawCover { 483 exec |= flatrpc.ExecFlagDedupCover 484 } 485 return flatrpc.ExecOpts{ 486 EnvFlags: env, 487 ExecFlags: exec, 488 SandboxArg: cfg.SandboxArg, 489 } 490 }