github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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 "sync" 12 "time" 13 14 "github.com/google/syzkaller/pkg/corpus" 15 "github.com/google/syzkaller/pkg/flatrpc" 16 "github.com/google/syzkaller/pkg/fuzzer/queue" 17 "github.com/google/syzkaller/pkg/ipc" 18 "github.com/google/syzkaller/pkg/stats" 19 "github.com/google/syzkaller/prog" 20 ) 21 22 type Fuzzer struct { 23 Stats 24 Config *Config 25 Cover *Cover 26 27 ctx context.Context 28 mu sync.Mutex 29 rnd *rand.Rand 30 target *prog.Target 31 32 ct *prog.ChoiceTable 33 ctProgs int 34 ctMu sync.Mutex // TODO: use RWLock. 35 ctRegenerate chan struct{} 36 37 execQueues 38 } 39 40 func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand, 41 target *prog.Target) *Fuzzer { 42 if cfg.NewInputFilter == nil { 43 cfg.NewInputFilter = func(call string) bool { 44 return true 45 } 46 } 47 f := &Fuzzer{ 48 Stats: newStats(), 49 Config: cfg, 50 Cover: newCover(), 51 52 ctx: ctx, 53 rnd: rnd, 54 target: target, 55 56 // We're okay to lose some of the messages -- if we are already 57 // regenerating the table, we don't want to repeat it right away. 58 ctRegenerate: make(chan struct{}), 59 } 60 f.execQueues = newExecQueues(f) 61 f.updateChoiceTable(nil) 62 go f.choiceTableUpdater() 63 if cfg.Debug { 64 go f.logCurrentStats() 65 } 66 return f 67 } 68 69 type execQueues struct { 70 smashQueue *queue.PlainQueue 71 triageQueue *queue.DynamicOrderer 72 candidateQueue *queue.PlainQueue 73 triageCandidateQueue *queue.DynamicOrderer 74 source queue.Source 75 } 76 77 func newExecQueues(fuzzer *Fuzzer) execQueues { 78 ret := execQueues{ 79 triageCandidateQueue: queue.DynamicOrder(), 80 candidateQueue: queue.PlainWithStat(fuzzer.StatCandidates), 81 triageQueue: queue.DynamicOrder(), 82 smashQueue: queue.Plain(), 83 } 84 // Sources are listed in the order, in which they will be polled. 85 ret.source = queue.Order( 86 ret.triageCandidateQueue, 87 ret.candidateQueue, 88 ret.triageQueue, 89 // Alternate smash jobs with exec/fuzz once in 3 times. 90 queue.Alternate(ret.smashQueue, 3), 91 queue.Callback(fuzzer.genFuzz), 92 ) 93 return ret 94 } 95 96 func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request) *queue.Result { 97 return fuzzer.executeWithFlags(executor, req, 0) 98 } 99 100 func (fuzzer *Fuzzer) executeWithFlags(executor queue.Executor, req *queue.Request, flags ProgTypes) *queue.Result { 101 executor.Submit(req) 102 res := req.Wait(fuzzer.ctx) 103 fuzzer.processResult(req, res, flags) 104 return res 105 } 106 107 func (fuzzer *Fuzzer) prepare(req *queue.Request, flags ProgTypes) { 108 req.OnDone(func(req *queue.Request, res *queue.Result) bool { 109 fuzzer.processResult(req, res, flags) 110 return true 111 }) 112 } 113 114 func (fuzzer *Fuzzer) enqueue(executor queue.Executor, req *queue.Request, flags ProgTypes) { 115 fuzzer.prepare(req, flags) 116 executor.Submit(req) 117 } 118 119 func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags ProgTypes) { 120 inTriage := flags&progInTriage > 0 121 // Triage individual calls. 122 // We do it before unblocking the waiting threads because 123 // it may result it concurrent modification of req.Prog. 124 // If we are already triaging this exact prog, this is flaky coverage. 125 if req.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectSignal > 0 && res.Info != nil && !inTriage { 126 for call, info := range res.Info.Calls { 127 fuzzer.triageProgCall(req.Prog, &info, call, flags) 128 } 129 fuzzer.triageProgCall(req.Prog, &res.Info.Extra, -1, flags) 130 } 131 if res.Info != nil { 132 fuzzer.statExecTime.Add(int(res.Info.Elapsed.Milliseconds())) 133 } 134 } 135 136 type Config struct { 137 Debug bool 138 Corpus *corpus.Corpus 139 BaseOpts ipc.ExecOpts // Fuzzer will use BaseOpts as a base for all requests. 140 Logf func(level int, msg string, args ...interface{}) 141 Coverage bool 142 FaultInjection bool 143 Comparisons bool 144 Collide bool 145 EnabledCalls map[*prog.Syscall]bool 146 NoMutateCalls map[int]bool 147 FetchRawCover bool 148 NewInputFilter func(call string) bool 149 } 150 151 func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *ipc.CallInfo, call int, flags ProgTypes) { 152 prio := signalPrio(p, info, call) 153 newMaxSignal := fuzzer.Cover.addRawMaxSignal(info.Signal, prio) 154 if newMaxSignal.Empty() { 155 return 156 } 157 if !fuzzer.Config.NewInputFilter(p.CallName(call)) { 158 return 159 } 160 fuzzer.Logf(2, "found new signal in call %d in %s", call, p) 161 162 queue := fuzzer.triageQueue 163 if flags&progCandidate > 0 { 164 queue = fuzzer.triageCandidateQueue 165 } 166 fuzzer.startJob(fuzzer.statJobsTriage, &triageJob{ 167 p: p.Clone(), 168 call: call, 169 info: *info, 170 newSignal: newMaxSignal, 171 flags: flags, 172 queue: queue.Append(), 173 }) 174 } 175 176 func signalPrio(p *prog.Prog, info *ipc.CallInfo, call int) (prio uint8) { 177 if call == -1 { 178 return 0 179 } 180 if info.Errno == 0 { 181 prio |= 1 << 1 182 } 183 if !p.Target.CallContainsAny(p.Calls[call]) { 184 prio |= 1 << 0 185 } 186 return 187 } 188 189 func (fuzzer *Fuzzer) genFuzz() *queue.Request { 190 // Either generate a new input or mutate an existing one. 191 mutateRate := 0.95 192 if !fuzzer.Config.Coverage { 193 // If we don't have real coverage signal, generate programs 194 // more frequently because fallback signal is weak. 195 mutateRate = 0.5 196 } 197 var req *queue.Request 198 rnd := fuzzer.rand() 199 if rnd.Float64() < mutateRate { 200 req = mutateProgRequest(fuzzer, rnd) 201 } 202 if req == nil { 203 req = genProgRequest(fuzzer, rnd) 204 } 205 fuzzer.prepare(req, 0) 206 return req 207 } 208 209 func (fuzzer *Fuzzer) startJob(stat *stats.Val, newJob job) { 210 fuzzer.Logf(2, "started %T", newJob) 211 go func() { 212 stat.Add(1) 213 fuzzer.statJobs.Add(1) 214 newJob.run(fuzzer) 215 fuzzer.statJobs.Add(-1) 216 stat.Add(-1) 217 }() 218 } 219 220 func (fuzzer *Fuzzer) Next() *queue.Request { 221 req := fuzzer.source.Next() 222 if req == nil { 223 // The fuzzer is not supposed to issue nil requests. 224 panic("nil request from the fuzzer") 225 } 226 req.ExecOpts = fuzzer.Config.BaseOpts.MergeFlags(req.ExecOpts) 227 return req 228 } 229 230 func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) { 231 if fuzzer.Config.Logf == nil { 232 return 233 } 234 fuzzer.Config.Logf(level, msg, args...) 235 } 236 237 type Candidate struct { 238 Prog *prog.Prog 239 Smashed bool 240 Minimized bool 241 } 242 243 func (fuzzer *Fuzzer) AddCandidates(candidates []Candidate) { 244 for _, candidate := range candidates { 245 req, flags := candidateRequest(fuzzer, candidate) 246 fuzzer.enqueue(fuzzer.candidateQueue, req, flags) 247 } 248 } 249 250 func (fuzzer *Fuzzer) rand() *rand.Rand { 251 fuzzer.mu.Lock() 252 defer fuzzer.mu.Unlock() 253 return rand.New(rand.NewSource(fuzzer.rnd.Int63())) 254 } 255 256 func (fuzzer *Fuzzer) updateChoiceTable(programs []*prog.Prog) { 257 newCt := fuzzer.target.BuildChoiceTable(programs, fuzzer.Config.EnabledCalls) 258 259 fuzzer.ctMu.Lock() 260 defer fuzzer.ctMu.Unlock() 261 if len(programs) >= fuzzer.ctProgs { 262 fuzzer.ctProgs = len(programs) 263 fuzzer.ct = newCt 264 } 265 } 266 267 func (fuzzer *Fuzzer) choiceTableUpdater() { 268 for { 269 select { 270 case <-fuzzer.ctx.Done(): 271 return 272 case <-fuzzer.ctRegenerate: 273 } 274 fuzzer.updateChoiceTable(fuzzer.Config.Corpus.Programs()) 275 } 276 } 277 278 func (fuzzer *Fuzzer) ChoiceTable() *prog.ChoiceTable { 279 progs := fuzzer.Config.Corpus.Programs() 280 281 fuzzer.ctMu.Lock() 282 defer fuzzer.ctMu.Unlock() 283 284 // There were no deep ideas nor any calculations behind these numbers. 285 regenerateEveryProgs := 333 286 if len(progs) < 100 { 287 regenerateEveryProgs = 33 288 } 289 if fuzzer.ctProgs+regenerateEveryProgs < len(progs) { 290 select { 291 case fuzzer.ctRegenerate <- struct{}{}: 292 default: 293 // We're okay to lose the message. 294 // It means that we're already regenerating the table. 295 } 296 } 297 return fuzzer.ct 298 } 299 300 func (fuzzer *Fuzzer) logCurrentStats() { 301 for { 302 select { 303 case <-time.After(time.Minute): 304 case <-fuzzer.ctx.Done(): 305 return 306 } 307 308 var m runtime.MemStats 309 runtime.ReadMemStats(&m) 310 311 str := fmt.Sprintf("running jobs: %d, heap (MB): %d", 312 fuzzer.statJobs.Val(), m.Alloc/1000/1000) 313 fuzzer.Logf(0, "%s", str) 314 } 315 } 316 317 func (fuzzer *Fuzzer) RotateMaxSignal(items int) { 318 corpusSignal := fuzzer.Config.Corpus.Signal() 319 pureMaxSignal := fuzzer.Cover.pureMaxSignal(corpusSignal) 320 if pureMaxSignal.Len() < items { 321 items = pureMaxSignal.Len() 322 } 323 fuzzer.Logf(1, "rotate %d max signal elements", items) 324 325 delta := pureMaxSignal.RandomSubset(fuzzer.rand(), items) 326 fuzzer.Cover.subtract(delta) 327 } 328 329 func setFlags(execFlags flatrpc.ExecFlag) ipc.ExecOpts { 330 return ipc.ExecOpts{ 331 ExecFlags: execFlags, 332 } 333 }