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  }