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  }