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  }