github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/fuzzer/queue/queue.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 queue 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/gob" 10 "errors" 11 "fmt" 12 "math/rand" 13 "strings" 14 "sync" 15 "sync/atomic" 16 17 "github.com/google/syzkaller/pkg/flatrpc" 18 "github.com/google/syzkaller/pkg/hash" 19 "github.com/google/syzkaller/pkg/stat" 20 "github.com/google/syzkaller/prog" 21 ) 22 23 type Request struct { 24 // Type of the request. 25 // RequestTypeProgram executes Prog, and is used by most requests (also the default zero value). 26 // RequestTypeBinary executes binary with file name stored in Data. 27 // RequestTypeGlob expands glob pattern stored in Data. 28 Type flatrpc.RequestType 29 ExecOpts flatrpc.ExecOpts 30 Prog *prog.Prog // for RequestTypeProgram 31 BinaryFile string // for RequestTypeBinary 32 GlobPattern string // for RequestTypeGlob 33 34 // Return all signal for these calls instead of new signal. 35 ReturnAllSignal []int 36 ReturnError bool 37 ReturnOutput bool 38 39 // This stat will be incremented on request completion. 40 Stat *stat.Val 41 42 // Important requests will be retried even from crashed VMs. 43 Important bool 44 45 // Avoid specifies set of executors that are preferable to avoid when executing this request. 46 // The restriction is soft since there can be only one executor at all or available right now. 47 Avoid []ExecutorID 48 49 // The callback will be called on request completion in the LIFO order. 50 // If it returns false, all further processing will be stopped. 51 // It allows wrappers to intercept Done() requests. 52 callback DoneCallback 53 54 onceCrashed bool 55 delayedSince uint64 56 57 mu sync.Mutex 58 result *Result 59 done chan struct{} 60 } 61 62 type ExecutorID struct { 63 VM int 64 Proc int 65 } 66 67 type DoneCallback func(*Request, *Result) bool 68 69 func (r *Request) OnDone(cb DoneCallback) { 70 oldCallback := r.callback 71 r.callback = func(req *Request, res *Result) bool { 72 r.callback = oldCallback 73 if !cb(req, res) { 74 return false 75 } 76 if oldCallback == nil { 77 return true 78 } 79 return oldCallback(req, res) 80 } 81 } 82 83 func (r *Request) Done(res *Result) { 84 if r.callback != nil { 85 if !r.callback(r, res) { 86 return 87 } 88 } 89 if r.Stat != nil { 90 r.Stat.Add(1) 91 } 92 r.initChannel() 93 r.result = res 94 close(r.done) 95 } 96 97 var ErrRequestAborted = errors.New("context closed while waiting the result") 98 99 // Wait() blocks until we have the result. 100 func (r *Request) Wait(ctx context.Context) *Result { 101 r.initChannel() 102 select { 103 case <-ctx.Done(): 104 return &Result{Status: ExecFailure, Err: ErrRequestAborted} 105 case <-r.done: 106 return r.result 107 } 108 } 109 110 // Risky() returns true if there's a substantial risk of the input crashing the VM. 111 func (r *Request) Risky() bool { 112 return r.onceCrashed 113 } 114 115 func (r *Request) Validate() error { 116 collectSignal := r.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectSignal > 0 117 if len(r.ReturnAllSignal) != 0 && !collectSignal { 118 return fmt.Errorf("ReturnAllSignal is set, but FlagCollectSignal is not") 119 } 120 collectComps := r.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectComps > 0 121 collectCover := r.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectCover > 0 122 if (collectComps) && (collectSignal || collectCover) { 123 return fmt.Errorf("hint collection is mutually exclusive with signal/coverage") 124 } 125 switch r.Type { 126 case flatrpc.RequestTypeProgram: 127 if r.Prog == nil { 128 return fmt.Errorf("program is not set") 129 } 130 sandboxes := flatrpc.ExecEnvSandboxNone | flatrpc.ExecEnvSandboxSetuid | 131 flatrpc.ExecEnvSandboxNamespace | flatrpc.ExecEnvSandboxAndroid 132 if r.ExecOpts.EnvFlags&sandboxes == 0 { 133 return fmt.Errorf("no sandboxes set") 134 } 135 case flatrpc.RequestTypeBinary: 136 if r.BinaryFile == "" { 137 return fmt.Errorf("binary file name is not set") 138 } 139 case flatrpc.RequestTypeGlob: 140 if r.GlobPattern == "" { 141 return fmt.Errorf("glob pattern is not set") 142 } 143 default: 144 return fmt.Errorf("unknown request type") 145 } 146 return nil 147 } 148 149 func (r *Request) hash() hash.Sig { 150 buf := new(bytes.Buffer) 151 enc := gob.NewEncoder(buf) 152 if err := enc.Encode(r.Type); err != nil { 153 panic(err) 154 } 155 if err := enc.Encode(r.ExecOpts); err != nil { 156 panic(err) 157 } 158 var data []byte 159 switch r.Type { 160 case flatrpc.RequestTypeProgram: 161 data = r.Prog.Serialize() 162 case flatrpc.RequestTypeBinary: 163 data = []byte(r.BinaryFile) 164 case flatrpc.RequestTypeGlob: 165 data = []byte(r.GlobPattern) 166 default: 167 panic("unknown request type") 168 } 169 return hash.Hash(data, buf.Bytes()) 170 } 171 172 func (r *Request) initChannel() { 173 r.mu.Lock() 174 if r.done == nil { 175 r.done = make(chan struct{}) 176 } 177 r.mu.Unlock() 178 } 179 180 type Result struct { 181 Info *flatrpc.ProgInfo 182 Executor ExecutorID 183 Output []byte 184 Status Status 185 Err error // More details in case of ExecFailure. 186 } 187 188 func (r *Result) clone() *Result { 189 ret := *r 190 ret.Info = ret.Info.Clone() 191 return &ret 192 } 193 194 func (r *Result) Stop() bool { 195 switch r.Status { 196 case Success, Restarted: 197 return false 198 case ExecFailure, Crashed, Hanged: 199 return true 200 default: 201 panic(fmt.Sprintf("unhandled status %v", r.Status)) 202 } 203 } 204 205 // Globs returns result of RequestTypeGlob. 206 func (r *Result) GlobFiles() []string { 207 out := strings.Trim(string(r.Output), "\000") 208 if out == "" { 209 return nil 210 } 211 return strings.Split(out, "\000") 212 } 213 214 type Status int 215 216 //go:generate go run golang.org/x/tools/cmd/stringer -type Status 217 const ( 218 Success Status = iota 219 ExecFailure // For e.g. serialization errors. 220 Crashed // The VM crashed holding the request. 221 Restarted // The VM was restarted holding the request. 222 Hanged // The program has hanged (can't be killed/waited). 223 ) 224 225 // Executor describes the interface wanted by the producers of requests. 226 // After a Request is submitted, it's expected that the consumer will eventually 227 // take it and report the execution result via Done(). 228 type Executor interface { 229 Submit(req *Request) 230 } 231 232 // Source describes the interface wanted by the consumers of requests. 233 type Source interface { 234 Next() *Request 235 } 236 237 // PlainQueue is a straighforward thread-safe Request queue implementation. 238 type PlainQueue struct { 239 mu sync.Mutex 240 queue []*Request 241 pos int 242 } 243 244 func Plain() *PlainQueue { 245 return &PlainQueue{} 246 } 247 248 func (pq *PlainQueue) Len() int { 249 pq.mu.Lock() 250 defer pq.mu.Unlock() 251 return len(pq.queue) - pq.pos 252 } 253 254 func (pq *PlainQueue) Submit(req *Request) { 255 pq.mu.Lock() 256 defer pq.mu.Unlock() 257 258 // It doesn't make sense to compact the queue too often. 259 const minSizeToCompact = 128 260 if pq.pos > len(pq.queue)/2 && len(pq.queue) >= minSizeToCompact { 261 copy(pq.queue, pq.queue[pq.pos:]) 262 for pq.pos > 0 { 263 newLen := len(pq.queue) - 1 264 pq.queue[newLen] = nil 265 pq.queue = pq.queue[:newLen] 266 pq.pos-- 267 } 268 } 269 pq.queue = append(pq.queue, req) 270 } 271 272 func (pq *PlainQueue) Next() *Request { 273 pq.mu.Lock() 274 defer pq.mu.Unlock() 275 return pq.nextLocked() 276 } 277 278 func (pq *PlainQueue) tryNext() *Request { 279 if !pq.mu.TryLock() { 280 return nil 281 } 282 defer pq.mu.Unlock() 283 return pq.nextLocked() 284 } 285 286 func (pq *PlainQueue) nextLocked() *Request { 287 if pq.pos == len(pq.queue) { 288 return nil 289 } 290 ret := pq.queue[pq.pos] 291 pq.queue[pq.pos] = nil 292 pq.pos++ 293 return ret 294 } 295 296 // Order combines several different sources in a particular order. 297 type orderImpl struct { 298 sources []Source 299 } 300 301 func Order(sources ...Source) Source { 302 return &orderImpl{sources: sources} 303 } 304 305 func (o *orderImpl) Next() *Request { 306 for _, s := range o.sources { 307 req := s.Next() 308 if req != nil { 309 return req 310 } 311 } 312 return nil 313 } 314 315 type callback struct { 316 cb func() *Request 317 } 318 319 // Callback produces a source that calls the callback to serve every Next() request. 320 func Callback(cb func() *Request) Source { 321 return &callback{cb} 322 } 323 324 func (cb *callback) Next() *Request { 325 return cb.cb() 326 } 327 328 type alternate struct { 329 base Source 330 nth int 331 seq atomic.Int64 332 } 333 334 // Alternate proxies base, but returns nil every nth Next() call. 335 func Alternate(base Source, nth int) Source { 336 return &alternate{ 337 base: base, 338 nth: nth, 339 } 340 } 341 342 func (a *alternate) Next() *Request { 343 if a.seq.Add(1)%int64(a.nth) == 0 { 344 return nil 345 } 346 return a.base.Next() 347 } 348 349 type DynamicOrderer struct { 350 mu sync.Mutex 351 currPrio int 352 ops *priorityQueueOps[*Request] 353 } 354 355 // DynamicOrder() can be used to form nested queues dynamically. 356 // That is, if 357 // q1 := pq.Append() 358 // q2 := pq.Append() 359 // All elements added via q2.Submit() will always have a *lower* priority 360 // than all elements added via q1.Submit(). 361 func DynamicOrder() *DynamicOrderer { 362 return &DynamicOrderer{ 363 ops: &priorityQueueOps[*Request]{}, 364 } 365 } 366 367 func (do *DynamicOrderer) Append() Executor { 368 do.mu.Lock() 369 defer do.mu.Unlock() 370 do.currPrio++ 371 return &dynamicOrdererItem{ 372 parent: do, 373 prio: do.currPrio, 374 } 375 } 376 377 func (do *DynamicOrderer) submit(req *Request, prio int) { 378 do.mu.Lock() 379 defer do.mu.Unlock() 380 do.ops.Push(req, prio) 381 } 382 383 func (do *DynamicOrderer) Next() *Request { 384 do.mu.Lock() 385 defer do.mu.Unlock() 386 return do.ops.Pop() 387 } 388 389 type dynamicOrdererItem struct { 390 parent *DynamicOrderer 391 prio int 392 } 393 394 func (doi *dynamicOrdererItem) Submit(req *Request) { 395 doi.parent.submit(req, doi.prio) 396 } 397 398 type DynamicSourceCtl struct { 399 value atomic.Pointer[Source] 400 } 401 402 // DynamicSource is assumed never to point to nil. 403 func DynamicSource(source Source) *DynamicSourceCtl { 404 var ret DynamicSourceCtl 405 ret.Store(source) 406 return &ret 407 } 408 409 func (ds *DynamicSourceCtl) Store(source Source) { 410 ds.value.Store(&source) 411 } 412 413 func (ds *DynamicSourceCtl) Next() *Request { 414 return (*ds.value.Load()).Next() 415 } 416 417 // Deduplicator() keeps track of the previously run requests to avoid re-running them. 418 type Deduplicator struct { 419 mu sync.Mutex 420 source Source 421 mm map[hash.Sig]*duplicateState 422 } 423 424 type duplicateState struct { 425 res *Result 426 queued []*Request // duplicate requests waiting for the result. 427 } 428 429 func Deduplicate(source Source) Source { 430 return &Deduplicator{ 431 source: source, 432 mm: map[hash.Sig]*duplicateState{}, 433 } 434 } 435 436 func (d *Deduplicator) Next() *Request { 437 for { 438 req := d.source.Next() 439 if req == nil { 440 return nil 441 } 442 hash := req.hash() 443 d.mu.Lock() 444 entry, ok := d.mm[hash] 445 if !ok { 446 d.mm[hash] = &duplicateState{} 447 } else if entry.res == nil { 448 // There's no result yet, put the request to the queue. 449 entry.queued = append(entry.queued, req) 450 } else { 451 // We already know the result. 452 req.Done(entry.res.clone()) 453 } 454 d.mu.Unlock() 455 if !ok { 456 // This is the first time we see such a request. 457 req.OnDone(d.onDone) 458 return req 459 } 460 } 461 } 462 463 func (d *Deduplicator) onDone(req *Request, res *Result) bool { 464 hash := req.hash() 465 clonedRes := res.clone() 466 467 d.mu.Lock() 468 entry := d.mm[hash] 469 queued := entry.queued 470 entry.queued = nil 471 entry.res = clonedRes 472 d.mu.Unlock() 473 474 // Broadcast the result. 475 for _, waitingReq := range queued { 476 waitingReq.Done(res.clone()) 477 } 478 return true 479 } 480 481 // DefaultOpts applies opts to all requests in source. 482 func DefaultOpts(source Source, opts flatrpc.ExecOpts) Source { 483 return &defaultOpts{source, opts} 484 } 485 486 type defaultOpts struct { 487 source Source 488 opts flatrpc.ExecOpts 489 } 490 491 func (do *defaultOpts) Next() *Request { 492 req := do.source.Next() 493 if req == nil { 494 return nil 495 } 496 req.ExecOpts.ExecFlags |= do.opts.ExecFlags 497 req.ExecOpts.EnvFlags |= do.opts.EnvFlags 498 req.ExecOpts.SandboxArg = do.opts.SandboxArg 499 return req 500 } 501 502 // RandomQueue holds up to |size| elements. 503 // Next() evicts a random one. 504 // On Submit(), if the queue is full, a random element is replaced. 505 type RandomQueue struct { 506 mu sync.Mutex 507 queue []*Request 508 maxSize int 509 rnd *rand.Rand 510 } 511 512 func NewRandomQueue(size int, rnd *rand.Rand) *RandomQueue { 513 return &RandomQueue{ 514 maxSize: size, 515 rnd: rnd, 516 } 517 } 518 519 func (rq *RandomQueue) Next() *Request { 520 rq.mu.Lock() 521 defer rq.mu.Unlock() 522 if len(rq.queue) == 0 { 523 return nil 524 } 525 pos := rq.rnd.Intn(len(rq.queue)) 526 item := rq.queue[pos] 527 528 last := len(rq.queue) - 1 529 rq.queue[pos] = rq.queue[last] 530 rq.queue[last] = nil 531 rq.queue = rq.queue[0 : len(rq.queue)-1] 532 return item 533 } 534 535 var errEvictedFromQueue = errors.New("evicted from the random queue") 536 537 func (rq *RandomQueue) Submit(req *Request) { 538 rq.mu.Lock() 539 defer rq.mu.Unlock() 540 if len(rq.queue) < rq.maxSize { 541 rq.queue = append(rq.queue, req) 542 } else { 543 pos := rq.rnd.Intn(rq.maxSize + 1) 544 if pos < len(rq.queue) { 545 rq.queue[pos].Done(&Result{ 546 Status: ExecFailure, 547 Err: errEvictedFromQueue, 548 }) 549 rq.queue[pos] = req 550 } 551 } 552 } 553 554 type tee struct { 555 queue Executor 556 src Source 557 } 558 559 func Tee(src Source, queue Executor) Source { 560 return &tee{src: src, queue: queue} 561 } 562 563 func (t *tee) Next() *Request { 564 req := t.src.Next() 565 if req == nil { 566 return nil 567 } 568 t.queue.Submit(&Request{ 569 // It makes little sense to copy other fields if these requests 570 // are to be executed in a different environment. 571 Type: req.Type, 572 ExecOpts: req.ExecOpts, 573 Prog: req.Prog.Clone(), 574 BinaryFile: req.BinaryFile, 575 GlobPattern: req.GlobPattern, 576 }) 577 return req 578 }