]> Cypherpunks.ru repositories - gostls13.git/blob - src/internal/fuzz/fuzz.go
8e4351e011f9b16e64f7c8f559383bdc3c30dd4e
[gostls13.git] / src / internal / fuzz / fuzz.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package fuzz provides common fuzzing functionality for tests built with
6 // "go test" and for programs that use fuzzing functionality in the testing
7 // package.
8 package fuzz
9
10 import (
11         "bytes"
12         "context"
13         "crypto/sha256"
14         "errors"
15         "fmt"
16         "internal/godebug"
17         "io"
18         "math/bits"
19         "os"
20         "path/filepath"
21         "reflect"
22         "runtime"
23         "strings"
24         "time"
25 )
26
27 // CoordinateFuzzingOpts is a set of arguments for CoordinateFuzzing.
28 // The zero value is valid for each field unless specified otherwise.
29 type CoordinateFuzzingOpts struct {
30         // Log is a writer for logging progress messages and warnings.
31         // If nil, io.Discard will be used instead.
32         Log io.Writer
33
34         // Timeout is the amount of wall clock time to spend fuzzing after the corpus
35         // has loaded. If zero, there will be no time limit.
36         Timeout time.Duration
37
38         // Limit is the number of random values to generate and test. If zero,
39         // there will be no limit on the number of generated values.
40         Limit int64
41
42         // MinimizeTimeout is the amount of wall clock time to spend minimizing
43         // after discovering a crasher. If zero, there will be no time limit. If
44         // MinimizeTimeout and MinimizeLimit are both zero, then minimization will
45         // be disabled.
46         MinimizeTimeout time.Duration
47
48         // MinimizeLimit is the maximum number of calls to the fuzz function to be
49         // made while minimizing after finding a crash. If zero, there will be no
50         // limit. Calls to the fuzz function made when minimizing also count toward
51         // Limit. If MinimizeTimeout and MinimizeLimit are both zero, then
52         // minimization will be disabled.
53         MinimizeLimit int64
54
55         // parallel is the number of worker processes to run in parallel. If zero,
56         // CoordinateFuzzing will run GOMAXPROCS workers.
57         Parallel int
58
59         // Seed is a list of seed values added by the fuzz target with testing.F.Add
60         // and in testdata.
61         Seed []CorpusEntry
62
63         // Types is the list of types which make up a corpus entry.
64         // Types must be set and must match values in Seed.
65         Types []reflect.Type
66
67         // CorpusDir is a directory where files containing values that crash the
68         // code being tested may be written. CorpusDir must be set.
69         CorpusDir string
70
71         // CacheDir is a directory containing additional "interesting" values.
72         // The fuzzer may derive new values from these, and may write new values here.
73         CacheDir string
74 }
75
76 // CoordinateFuzzing creates several worker processes and communicates with
77 // them to test random inputs that could trigger crashes and expose bugs.
78 // The worker processes run the same binary in the same directory with the
79 // same environment variables as the coordinator process. Workers also run
80 // with the same arguments as the coordinator, except with the -test.fuzzworker
81 // flag prepended to the argument list.
82 //
83 // If a crash occurs, the function will return an error containing information
84 // about the crash, which can be reported to the user.
85 func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err error) {
86         if err := ctx.Err(); err != nil {
87                 return err
88         }
89         if opts.Log == nil {
90                 opts.Log = io.Discard
91         }
92         if opts.Parallel == 0 {
93                 opts.Parallel = runtime.GOMAXPROCS(0)
94         }
95         if opts.Limit > 0 && int64(opts.Parallel) > opts.Limit {
96                 // Don't start more workers than we need.
97                 opts.Parallel = int(opts.Limit)
98         }
99
100         c, err := newCoordinator(opts)
101         if err != nil {
102                 return err
103         }
104
105         if opts.Timeout > 0 {
106                 var cancel func()
107                 ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
108                 defer cancel()
109         }
110
111         // fuzzCtx is used to stop workers, for example, after finding a crasher.
112         fuzzCtx, cancelWorkers := context.WithCancel(ctx)
113         defer cancelWorkers()
114         doneC := ctx.Done()
115
116         // stop is called when a worker encounters a fatal error.
117         var fuzzErr error
118         stopping := false
119         stop := func(err error) {
120                 if shouldPrintDebugInfo() {
121                         _, file, line, ok := runtime.Caller(1)
122                         if ok {
123                                 c.debugLogf("stop called at %s:%d. stopping: %t", file, line, stopping)
124                         } else {
125                                 c.debugLogf("stop called at unknown. stopping: %t", stopping)
126                         }
127                 }
128
129                 if err == fuzzCtx.Err() || isInterruptError(err) {
130                         // Suppress cancellation errors and terminations due to SIGINT.
131                         // The messages are not helpful since either the user triggered the error
132                         // (with ^C) or another more helpful message will be printed (a crasher).
133                         err = nil
134                 }
135                 if err != nil && (fuzzErr == nil || fuzzErr == ctx.Err()) {
136                         fuzzErr = err
137                 }
138                 if stopping {
139                         return
140                 }
141                 stopping = true
142                 cancelWorkers()
143                 doneC = nil
144         }
145
146         // Ensure that any crash we find is written to the corpus, even if an error
147         // or interruption occurs while minimizing it.
148         crashWritten := false
149         defer func() {
150                 if c.crashMinimizing == nil || crashWritten {
151                         return
152                 }
153                 werr := writeToCorpus(&c.crashMinimizing.entry, opts.CorpusDir)
154                 if werr != nil {
155                         err = fmt.Errorf("%w\n%v", err, werr)
156                         return
157                 }
158                 if err == nil {
159                         err = &crashError{
160                                 path: c.crashMinimizing.entry.Path,
161                                 err:  errors.New(c.crashMinimizing.crasherMsg),
162                         }
163                 }
164         }()
165
166         // Start workers.
167         // TODO(jayconrod): do we want to support fuzzing different binaries?
168         dir := "" // same as self
169         binPath := os.Args[0]
170         args := append([]string{"-test.fuzzworker"}, os.Args[1:]...)
171         env := os.Environ() // same as self
172
173         errC := make(chan error)
174         workers := make([]*worker, opts.Parallel)
175         for i := range workers {
176                 var err error
177                 workers[i], err = newWorker(c, dir, binPath, args, env)
178                 if err != nil {
179                         return err
180                 }
181         }
182         for i := range workers {
183                 w := workers[i]
184                 go func() {
185                         err := w.coordinate(fuzzCtx)
186                         if fuzzCtx.Err() != nil || isInterruptError(err) {
187                                 err = nil
188                         }
189                         cleanErr := w.cleanup()
190                         if err == nil {
191                                 err = cleanErr
192                         }
193                         errC <- err
194                 }()
195         }
196
197         // Main event loop.
198         // Do not return until all workers have terminated. We avoid a deadlock by
199         // receiving messages from workers even after ctx is cancelled.
200         activeWorkers := len(workers)
201         statTicker := time.NewTicker(3 * time.Second)
202         defer statTicker.Stop()
203         defer c.logStats()
204
205         c.logStats()
206         for {
207                 // If there is an execution limit, and we've reached it, stop.
208                 if c.opts.Limit > 0 && c.count >= c.opts.Limit {
209                         stop(nil)
210                 }
211
212                 var inputC chan fuzzInput
213                 input, ok := c.peekInput()
214                 if ok && c.crashMinimizing == nil && !stopping {
215                         inputC = c.inputC
216                 }
217
218                 var minimizeC chan fuzzMinimizeInput
219                 minimizeInput, ok := c.peekMinimizeInput()
220                 if ok && !stopping {
221                         minimizeC = c.minimizeC
222                 }
223
224                 select {
225                 case <-doneC:
226                         // Interrupted, cancelled, or timed out.
227                         // stop sets doneC to nil so we don't busy wait here.
228                         stop(ctx.Err())
229
230                 case err := <-errC:
231                         // A worker terminated, possibly after encountering a fatal error.
232                         stop(err)
233                         activeWorkers--
234                         if activeWorkers == 0 {
235                                 return fuzzErr
236                         }
237
238                 case result := <-c.resultC:
239                         // Received response from worker.
240                         if stopping {
241                                 break
242                         }
243                         c.updateStats(result)
244
245                         if result.crasherMsg != "" {
246                                 if c.warmupRun() && result.entry.IsSeed {
247                                         target := filepath.Base(c.opts.CorpusDir)
248                                         fmt.Fprintf(c.opts.Log, "failure while testing seed corpus entry: %s/%s\n", target, testName(result.entry.Parent))
249                                         stop(errors.New(result.crasherMsg))
250                                         break
251                                 }
252                                 if c.canMinimize() && result.canMinimize {
253                                         if c.crashMinimizing != nil {
254                                                 // This crash is not minimized, and another crash is being minimized.
255                                                 // Ignore this one and wait for the other one to finish.
256                                                 if shouldPrintDebugInfo() {
257                                                         c.debugLogf("found unminimized crasher, skipping in favor of minimizable crasher")
258                                                 }
259                                                 break
260                                         }
261                                         // Found a crasher but haven't yet attempted to minimize it.
262                                         // Send it back to a worker for minimization. Disable inputC so
263                                         // other workers don't continue fuzzing.
264                                         c.crashMinimizing = &result
265                                         fmt.Fprintf(c.opts.Log, "fuzz: minimizing %d-byte failing input file\n", len(result.entry.Data))
266                                         c.queueForMinimization(result, nil)
267                                 } else if !crashWritten {
268                                         // Found a crasher that's either minimized or not minimizable.
269                                         // Write to corpus and stop.
270                                         err := writeToCorpus(&result.entry, opts.CorpusDir)
271                                         if err == nil {
272                                                 crashWritten = true
273                                                 err = &crashError{
274                                                         path: result.entry.Path,
275                                                         err:  errors.New(result.crasherMsg),
276                                                 }
277                                         }
278                                         if shouldPrintDebugInfo() {
279                                                 c.debugLogf(
280                                                         "found crasher, id: %s, parent: %s, gen: %d, size: %d, exec time: %s",
281                                                         result.entry.Path,
282                                                         result.entry.Parent,
283                                                         result.entry.Generation,
284                                                         len(result.entry.Data),
285                                                         result.entryDuration,
286                                                 )
287                                         }
288                                         stop(err)
289                                 }
290                         } else if result.coverageData != nil {
291                                 if c.warmupRun() {
292                                         if shouldPrintDebugInfo() {
293                                                 c.debugLogf(
294                                                         "processed an initial input, id: %s, new bits: %d, size: %d, exec time: %s",
295                                                         result.entry.Parent,
296                                                         countBits(diffCoverage(c.coverageMask, result.coverageData)),
297                                                         len(result.entry.Data),
298                                                         result.entryDuration,
299                                                 )
300                                         }
301                                         c.updateCoverage(result.coverageData)
302                                         c.warmupInputLeft--
303                                         if c.warmupInputLeft == 0 {
304                                                 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
305                                                 if shouldPrintDebugInfo() {
306                                                         c.debugLogf(
307                                                                 "finished processing input corpus, entries: %d, initial coverage bits: %d",
308                                                                 len(c.corpus.entries),
309                                                                 countBits(c.coverageMask),
310                                                         )
311                                                 }
312                                         }
313                                 } else if keepCoverage := diffCoverage(c.coverageMask, result.coverageData); keepCoverage != nil {
314                                         // Found a value that expanded coverage.
315                                         // It's not a crasher, but we may want to add it to the on-disk
316                                         // corpus and prioritize it for future fuzzing.
317                                         // TODO(jayconrod, katiehockman): Prioritize fuzzing these
318                                         // values which expanded coverage, perhaps based on the
319                                         // number of new edges that this result expanded.
320                                         // TODO(jayconrod, katiehockman): Don't write a value that's already
321                                         // in the corpus.
322                                         if c.canMinimize() && result.canMinimize && c.crashMinimizing == nil {
323                                                 // Send back to workers to find a smaller value that preserves
324                                                 // at least one new coverage bit.
325                                                 c.queueForMinimization(result, keepCoverage)
326                                         } else {
327                                                 // Update the coordinator's coverage mask and save the value.
328                                                 inputSize := len(result.entry.Data)
329                                                 entryNew, err := c.addCorpusEntries(true, result.entry)
330                                                 if err != nil {
331                                                         stop(err)
332                                                         break
333                                                 }
334                                                 if !entryNew {
335                                                         if shouldPrintDebugInfo() {
336                                                                 c.debugLogf(
337                                                                         "ignoring duplicate input which increased coverage, id: %s",
338                                                                         result.entry.Path,
339                                                                 )
340                                                         }
341                                                         break
342                                                 }
343                                                 c.updateCoverage(keepCoverage)
344                                                 c.inputQueue.enqueue(result.entry)
345                                                 c.interestingCount++
346                                                 if shouldPrintDebugInfo() {
347                                                         c.debugLogf(
348                                                                 "new interesting input, id: %s, parent: %s, gen: %d, new bits: %d, total bits: %d, size: %d, exec time: %s",
349                                                                 result.entry.Path,
350                                                                 result.entry.Parent,
351                                                                 result.entry.Generation,
352                                                                 countBits(keepCoverage),
353                                                                 countBits(c.coverageMask),
354                                                                 inputSize,
355                                                                 result.entryDuration,
356                                                         )
357                                                 }
358                                         }
359                                 } else {
360                                         if shouldPrintDebugInfo() {
361                                                 c.debugLogf(
362                                                         "worker reported interesting input that doesn't expand coverage, id: %s, parent: %s, canMinimize: %t",
363                                                         result.entry.Path,
364                                                         result.entry.Parent,
365                                                         result.canMinimize,
366                                                 )
367                                         }
368                                 }
369                         } else if c.warmupRun() {
370                                 // No error or coverage data was reported for this input during
371                                 // warmup, so continue processing results.
372                                 c.warmupInputLeft--
373                                 if c.warmupInputLeft == 0 {
374                                         fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
375                                         if shouldPrintDebugInfo() {
376                                                 c.debugLogf(
377                                                         "finished testing-only phase, entries: %d",
378                                                         len(c.corpus.entries),
379                                                 )
380                                         }
381                                 }
382                         }
383
384                 case inputC <- input:
385                         // Sent the next input to a worker.
386                         c.sentInput(input)
387
388                 case minimizeC <- minimizeInput:
389                         // Sent the next input for minimization to a worker.
390                         c.sentMinimizeInput(minimizeInput)
391
392                 case <-statTicker.C:
393                         c.logStats()
394                 }
395         }
396
397         // TODO(jayconrod,katiehockman): if a crasher can't be written to the corpus,
398         // write to the cache instead.
399 }
400
401 // crashError wraps a crasher written to the seed corpus. It saves the name
402 // of the file where the input causing the crasher was saved. The testing
403 // framework uses this to report a command to re-run that specific input.
404 type crashError struct {
405         path string
406         err  error
407 }
408
409 func (e *crashError) Error() string {
410         return e.err.Error()
411 }
412
413 func (e *crashError) Unwrap() error {
414         return e.err
415 }
416
417 func (e *crashError) CrashPath() string {
418         return e.path
419 }
420
421 type corpus struct {
422         entries []CorpusEntry
423         hashes  map[[sha256.Size]byte]bool
424 }
425
426 // addCorpusEntries adds entries to the corpus, and optionally writes the entries
427 // to the cache directory. If an entry is already in the corpus it is skipped. If
428 // all of the entries are unique, addCorpusEntries returns true and a nil error,
429 // if at least one of the entries was a duplicate, it returns false and a nil error.
430 func (c *coordinator) addCorpusEntries(addToCache bool, entries ...CorpusEntry) (bool, error) {
431         noDupes := true
432         for _, e := range entries {
433                 data, err := corpusEntryData(e)
434                 if err != nil {
435                         return false, err
436                 }
437                 h := sha256.Sum256(data)
438                 if c.corpus.hashes[h] {
439                         noDupes = false
440                         continue
441                 }
442                 if addToCache {
443                         if err := writeToCorpus(&e, c.opts.CacheDir); err != nil {
444                                 return false, err
445                         }
446                         // For entries written to disk, we don't hold onto the bytes,
447                         // since the corpus would consume a significant amount of
448                         // memory.
449                         e.Data = nil
450                 }
451                 c.corpus.hashes[h] = true
452                 c.corpus.entries = append(c.corpus.entries, e)
453         }
454         return noDupes, nil
455 }
456
457 // CorpusEntry represents an individual input for fuzzing.
458 //
459 // We must use an equivalent type in the testing and testing/internal/testdeps
460 // packages, but testing can't import this package directly, and we don't want
461 // to export this type from testing. Instead, we use the same struct type and
462 // use a type alias (not a defined type) for convenience.
463 type CorpusEntry = struct {
464         Parent string
465
466         // Path is the path of the corpus file, if the entry was loaded from disk.
467         // For other entries, including seed values provided by f.Add, Path is the
468         // name of the test, e.g. seed#0 or its hash.
469         Path string
470
471         // Data is the raw input data. Data should only be populated for seed
472         // values. For on-disk corpus files, Data will be nil, as it will be loaded
473         // from disk using Path.
474         Data []byte
475
476         // Values is the unmarshaled values from a corpus file.
477         Values []any
478
479         Generation int
480
481         // IsSeed indicates whether this entry is part of the seed corpus.
482         IsSeed bool
483 }
484
485 // corpusEntryData returns the raw input bytes, either from the data struct
486 // field, or from disk.
487 func corpusEntryData(ce CorpusEntry) ([]byte, error) {
488         if ce.Data != nil {
489                 return ce.Data, nil
490         }
491
492         return os.ReadFile(ce.Path)
493 }
494
495 type fuzzInput struct {
496         // entry is the value to test initially. The worker will randomly mutate
497         // values from this starting point.
498         entry CorpusEntry
499
500         // timeout is the time to spend fuzzing variations of this input,
501         // not including starting or cleaning up.
502         timeout time.Duration
503
504         // limit is the maximum number of calls to the fuzz function the worker may
505         // make. The worker may make fewer calls, for example, if it finds an
506         // error early. If limit is zero, there is no limit on calls to the
507         // fuzz function.
508         limit int64
509
510         // warmup indicates whether this is a warmup input before fuzzing begins. If
511         // true, the input should not be fuzzed.
512         warmup bool
513
514         // coverageData reflects the coordinator's current coverageMask.
515         coverageData []byte
516 }
517
518 type fuzzResult struct {
519         // entry is an interesting value or a crasher.
520         entry CorpusEntry
521
522         // crasherMsg is an error message from a crash. It's "" if no crash was found.
523         crasherMsg string
524
525         // canMinimize is true if the worker should attempt to minimize this result.
526         // It may be false because an attempt has already been made.
527         canMinimize bool
528
529         // coverageData is set if the worker found new coverage.
530         coverageData []byte
531
532         // limit is the number of values the coordinator asked the worker
533         // to test. 0 if there was no limit.
534         limit int64
535
536         // count is the number of values the worker actually tested.
537         count int64
538
539         // totalDuration is the time the worker spent testing inputs.
540         totalDuration time.Duration
541
542         // entryDuration is the time the worker spent execution an interesting result
543         entryDuration time.Duration
544 }
545
546 type fuzzMinimizeInput struct {
547         // entry is an interesting value or crasher to minimize.
548         entry CorpusEntry
549
550         // crasherMsg is an error message from a crash. It's "" if no crash was found.
551         // If set, the worker will attempt to find a smaller input that also produces
552         // an error, though not necessarily the same error.
553         crasherMsg string
554
555         // limit is the maximum number of calls to the fuzz function the worker may
556         // make. The worker may make fewer calls, for example, if it can't reproduce
557         // an error. If limit is zero, there is no limit on calls to the fuzz function.
558         limit int64
559
560         // timeout is the time to spend minimizing this input.
561         // A zero timeout means no limit.
562         timeout time.Duration
563
564         // keepCoverage is a set of coverage bits that entry found that were not in
565         // the coordinator's combined set. When minimizing, the worker should find an
566         // input that preserves at least one of these bits. keepCoverage is nil for
567         // crashing inputs.
568         keepCoverage []byte
569 }
570
571 // coordinator holds channels that workers can use to communicate with
572 // the coordinator.
573 type coordinator struct {
574         opts CoordinateFuzzingOpts
575
576         // startTime is the time we started the workers after loading the corpus.
577         // Used for logging.
578         startTime time.Time
579
580         // inputC is sent values to fuzz by the coordinator. Any worker may receive
581         // values from this channel. Workers send results to resultC.
582         inputC chan fuzzInput
583
584         // minimizeC is sent values to minimize by the coordinator. Any worker may
585         // receive values from this channel. Workers send results to resultC.
586         minimizeC chan fuzzMinimizeInput
587
588         // resultC is sent results of fuzzing by workers. The coordinator
589         // receives these. Multiple types of messages are allowed.
590         resultC chan fuzzResult
591
592         // count is the number of values fuzzed so far.
593         count int64
594
595         // countLastLog is the number of values fuzzed when the output was last
596         // logged.
597         countLastLog int64
598
599         // timeLastLog is the time at which the output was last logged.
600         timeLastLog time.Time
601
602         // interestingCount is the number of unique interesting values which have
603         // been found this execution.
604         interestingCount int
605
606         // warmupInputCount is the count of all entries in the corpus which will
607         // need to be received from workers to run once during warmup, but not fuzz.
608         // This could be for coverage data, or only for the purposes of verifying
609         // that the seed corpus doesn't have any crashers. See warmupRun.
610         warmupInputCount int
611
612         // warmupInputLeft is the number of entries in the corpus which still need
613         // to be received from workers to run once during warmup, but not fuzz.
614         // See warmupInputLeft.
615         warmupInputLeft int
616
617         // duration is the time spent fuzzing inside workers, not counting time
618         // starting up or tearing down.
619         duration time.Duration
620
621         // countWaiting is the number of fuzzing executions the coordinator is
622         // waiting on workers to complete.
623         countWaiting int64
624
625         // corpus is a set of interesting values, including the seed corpus and
626         // generated values that workers reported as interesting.
627         corpus corpus
628
629         // minimizationAllowed is true if one or more of the types of fuzz
630         // function's parameters can be minimized.
631         minimizationAllowed bool
632
633         // inputQueue is a queue of inputs that workers should try fuzzing. This is
634         // initially populated from the seed corpus and cached inputs. More inputs
635         // may be added as new coverage is discovered.
636         inputQueue queue
637
638         // minimizeQueue is a queue of inputs that caused errors or exposed new
639         // coverage. Workers should attempt to find smaller inputs that do the
640         // same thing.
641         minimizeQueue queue
642
643         // crashMinimizing is the crash that is currently being minimized.
644         crashMinimizing *fuzzResult
645
646         // coverageMask aggregates coverage that was found for all inputs in the
647         // corpus. Each byte represents a single basic execution block. Each set bit
648         // within the byte indicates that an input has triggered that block at least
649         // 1 << n times, where n is the position of the bit in the byte. For example, a
650         // value of 12 indicates that separate inputs have triggered this block
651         // between 4-7 times and 8-15 times.
652         coverageMask []byte
653 }
654
655 func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
656         // Make sure all of the seed corpus has marshalled data.
657         for i := range opts.Seed {
658                 if opts.Seed[i].Data == nil && opts.Seed[i].Values != nil {
659                         opts.Seed[i].Data = marshalCorpusFile(opts.Seed[i].Values...)
660                 }
661         }
662         c := &coordinator{
663                 opts:        opts,
664                 startTime:   time.Now(),
665                 inputC:      make(chan fuzzInput),
666                 minimizeC:   make(chan fuzzMinimizeInput),
667                 resultC:     make(chan fuzzResult),
668                 timeLastLog: time.Now(),
669                 corpus:      corpus{hashes: make(map[[sha256.Size]byte]bool)},
670         }
671         if err := c.readCache(); err != nil {
672                 return nil, err
673         }
674         if opts.MinimizeLimit > 0 || opts.MinimizeTimeout > 0 {
675                 for _, t := range opts.Types {
676                         if isMinimizable(t) {
677                                 c.minimizationAllowed = true
678                                 break
679                         }
680                 }
681         }
682
683         covSize := len(coverage())
684         if covSize == 0 {
685                 fmt.Fprintf(c.opts.Log, "warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient\n")
686                 // Even though a coverage-only run won't occur, we should still run all
687                 // of the seed corpus to make sure there are no existing failures before
688                 // we start fuzzing.
689                 c.warmupInputCount = len(c.opts.Seed)
690                 for _, e := range c.opts.Seed {
691                         c.inputQueue.enqueue(e)
692                 }
693         } else {
694                 c.warmupInputCount = len(c.corpus.entries)
695                 for _, e := range c.corpus.entries {
696                         c.inputQueue.enqueue(e)
697                 }
698                 // Set c.coverageMask to a clean []byte full of zeros.
699                 c.coverageMask = make([]byte, covSize)
700         }
701         c.warmupInputLeft = c.warmupInputCount
702
703         if len(c.corpus.entries) == 0 {
704                 fmt.Fprintf(c.opts.Log, "warning: starting with empty corpus\n")
705                 var vals []any
706                 for _, t := range opts.Types {
707                         vals = append(vals, zeroValue(t))
708                 }
709                 data := marshalCorpusFile(vals...)
710                 h := sha256.Sum256(data)
711                 name := fmt.Sprintf("%x", h[:4])
712                 c.addCorpusEntries(false, CorpusEntry{Path: name, Data: data})
713         }
714
715         return c, nil
716 }
717
718 func (c *coordinator) updateStats(result fuzzResult) {
719         c.count += result.count
720         c.countWaiting -= result.limit
721         c.duration += result.totalDuration
722 }
723
724 func (c *coordinator) logStats() {
725         now := time.Now()
726         if c.warmupRun() {
727                 runSoFar := c.warmupInputCount - c.warmupInputLeft
728                 if coverageEnabled {
729                         fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
730                 } else {
731                         fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
732                 }
733         } else if c.crashMinimizing != nil {
734                 fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, minimizing\n", c.elapsed())
735         } else {
736                 rate := float64(c.count-c.countLastLog) / now.Sub(c.timeLastLog).Seconds()
737                 if coverageEnabled {
738                         total := c.warmupInputCount + c.interestingCount
739                         fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec), new interesting: %d (total: %d)\n", c.elapsed(), c.count, rate, c.interestingCount, total)
740                 } else {
741                         fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec)\n", c.elapsed(), c.count, rate)
742                 }
743         }
744         c.countLastLog = c.count
745         c.timeLastLog = now
746 }
747
748 // peekInput returns the next value that should be sent to workers.
749 // If the number of executions is limited, the returned value includes
750 // a limit for one worker. If there are no executions left, peekInput returns
751 // a zero value and false.
752 //
753 // peekInput doesn't actually remove the input from the queue. The caller
754 // must call sentInput after sending the input.
755 //
756 // If the input queue is empty and the coverage/testing-only run has completed,
757 // queue refills it from the corpus.
758 func (c *coordinator) peekInput() (fuzzInput, bool) {
759         if c.opts.Limit > 0 && c.count+c.countWaiting >= c.opts.Limit {
760                 // Already making the maximum number of calls to the fuzz function.
761                 // Don't send more inputs right now.
762                 return fuzzInput{}, false
763         }
764         if c.inputQueue.len == 0 {
765                 if c.warmupRun() {
766                         // Wait for coverage/testing-only run to finish before sending more
767                         // inputs.
768                         return fuzzInput{}, false
769                 }
770                 c.refillInputQueue()
771         }
772
773         entry, ok := c.inputQueue.peek()
774         if !ok {
775                 panic("input queue empty after refill")
776         }
777         input := fuzzInput{
778                 entry:   entry.(CorpusEntry),
779                 timeout: workerFuzzDuration,
780                 warmup:  c.warmupRun(),
781         }
782         if c.coverageMask != nil {
783                 input.coverageData = bytes.Clone(c.coverageMask)
784         }
785         if input.warmup {
786                 // No fuzzing will occur, but it should count toward the limit set by
787                 // -fuzztime.
788                 input.limit = 1
789                 return input, true
790         }
791
792         if c.opts.Limit > 0 {
793                 input.limit = c.opts.Limit / int64(c.opts.Parallel)
794                 if c.opts.Limit%int64(c.opts.Parallel) > 0 {
795                         input.limit++
796                 }
797                 remaining := c.opts.Limit - c.count - c.countWaiting
798                 if input.limit > remaining {
799                         input.limit = remaining
800                 }
801         }
802         return input, true
803 }
804
805 // sentInput updates internal counters after an input is sent to c.inputC.
806 func (c *coordinator) sentInput(input fuzzInput) {
807         c.inputQueue.dequeue()
808         c.countWaiting += input.limit
809 }
810
811 // refillInputQueue refills the input queue from the corpus after it becomes
812 // empty.
813 func (c *coordinator) refillInputQueue() {
814         for _, e := range c.corpus.entries {
815                 c.inputQueue.enqueue(e)
816         }
817 }
818
819 // queueForMinimization creates a fuzzMinimizeInput from result and adds it
820 // to the minimization queue to be sent to workers.
821 func (c *coordinator) queueForMinimization(result fuzzResult, keepCoverage []byte) {
822         if shouldPrintDebugInfo() {
823                 c.debugLogf(
824                         "queueing input for minimization, id: %s, parent: %s, keepCoverage: %t, crasher: %t",
825                         result.entry.Path,
826                         result.entry.Parent,
827                         keepCoverage != nil,
828                         result.crasherMsg != "",
829                 )
830         }
831         if result.crasherMsg != "" {
832                 c.minimizeQueue.clear()
833         }
834
835         input := fuzzMinimizeInput{
836                 entry:        result.entry,
837                 crasherMsg:   result.crasherMsg,
838                 keepCoverage: keepCoverage,
839         }
840         c.minimizeQueue.enqueue(input)
841 }
842
843 // peekMinimizeInput returns the next input that should be sent to workers for
844 // minimization.
845 func (c *coordinator) peekMinimizeInput() (fuzzMinimizeInput, bool) {
846         if !c.canMinimize() {
847                 // Already making the maximum number of calls to the fuzz function.
848                 // Don't send more inputs right now.
849                 return fuzzMinimizeInput{}, false
850         }
851         v, ok := c.minimizeQueue.peek()
852         if !ok {
853                 return fuzzMinimizeInput{}, false
854         }
855         input := v.(fuzzMinimizeInput)
856
857         if c.opts.MinimizeTimeout > 0 {
858                 input.timeout = c.opts.MinimizeTimeout
859         }
860         if c.opts.MinimizeLimit > 0 {
861                 input.limit = c.opts.MinimizeLimit
862         } else if c.opts.Limit > 0 {
863                 if input.crasherMsg != "" {
864                         input.limit = c.opts.Limit
865                 } else {
866                         input.limit = c.opts.Limit / int64(c.opts.Parallel)
867                         if c.opts.Limit%int64(c.opts.Parallel) > 0 {
868                                 input.limit++
869                         }
870                 }
871         }
872         if c.opts.Limit > 0 {
873                 remaining := c.opts.Limit - c.count - c.countWaiting
874                 if input.limit > remaining {
875                         input.limit = remaining
876                 }
877         }
878         return input, true
879 }
880
881 // sentMinimizeInput removes an input from the minimization queue after it's
882 // sent to minimizeC.
883 func (c *coordinator) sentMinimizeInput(input fuzzMinimizeInput) {
884         c.minimizeQueue.dequeue()
885         c.countWaiting += input.limit
886 }
887
888 // warmupRun returns true while the coordinator is running inputs without
889 // mutating them as a warmup before fuzzing. This could be to gather baseline
890 // coverage data for entries in the corpus, or to test all of the seed corpus
891 // for errors before fuzzing begins.
892 //
893 // The coordinator doesn't store coverage data in the cache with each input
894 // because that data would be invalid when counter offsets in the test binary
895 // change.
896 //
897 // When gathering coverage, the coordinator sends each entry to a worker to
898 // gather coverage for that entry only, without fuzzing or minimizing. This
899 // phase ends when all workers have finished, and the coordinator has a combined
900 // coverage map.
901 func (c *coordinator) warmupRun() bool {
902         return c.warmupInputLeft > 0
903 }
904
905 // updateCoverage sets bits in c.coverageMask that are set in newCoverage.
906 // updateCoverage returns the number of newly set bits. See the comment on
907 // coverageMask for the format.
908 func (c *coordinator) updateCoverage(newCoverage []byte) int {
909         if len(newCoverage) != len(c.coverageMask) {
910                 panic(fmt.Sprintf("number of coverage counters changed at runtime: %d, expected %d", len(newCoverage), len(c.coverageMask)))
911         }
912         newBitCount := 0
913         for i := range newCoverage {
914                 diff := newCoverage[i] &^ c.coverageMask[i]
915                 newBitCount += bits.OnesCount8(diff)
916                 c.coverageMask[i] |= newCoverage[i]
917         }
918         return newBitCount
919 }
920
921 // canMinimize returns whether the coordinator should attempt to find smaller
922 // inputs that reproduce a crash or new coverage.
923 func (c *coordinator) canMinimize() bool {
924         return c.minimizationAllowed &&
925                 (c.opts.Limit == 0 || c.count+c.countWaiting < c.opts.Limit)
926 }
927
928 func (c *coordinator) elapsed() time.Duration {
929         return time.Since(c.startTime).Round(1 * time.Second)
930 }
931
932 // readCache creates a combined corpus from seed values and values in the cache
933 // (in GOCACHE/fuzz).
934 //
935 // TODO(fuzzing): need a mechanism that can remove values that
936 // aren't useful anymore, for example, because they have the wrong type.
937 func (c *coordinator) readCache() error {
938         if _, err := c.addCorpusEntries(false, c.opts.Seed...); err != nil {
939                 return err
940         }
941         entries, err := ReadCorpus(c.opts.CacheDir, c.opts.Types)
942         if err != nil {
943                 if _, ok := err.(*MalformedCorpusError); !ok {
944                         // It's okay if some files in the cache directory are malformed and
945                         // are not included in the corpus, but fail if it's an I/O error.
946                         return err
947                 }
948                 // TODO(jayconrod,katiehockman): consider printing some kind of warning
949                 // indicating the number of files which were skipped because they are
950                 // malformed.
951         }
952         if _, err := c.addCorpusEntries(false, entries...); err != nil {
953                 return err
954         }
955         return nil
956 }
957
958 // MalformedCorpusError is an error found while reading the corpus from the
959 // filesystem. All of the errors are stored in the errs list. The testing
960 // framework uses this to report malformed files in testdata.
961 type MalformedCorpusError struct {
962         errs []error
963 }
964
965 func (e *MalformedCorpusError) Error() string {
966         var msgs []string
967         for _, s := range e.errs {
968                 msgs = append(msgs, s.Error())
969         }
970         return strings.Join(msgs, "\n")
971 }
972
973 // ReadCorpus reads the corpus from the provided dir. The returned corpus
974 // entries are guaranteed to match the given types. Any malformed files will
975 // be saved in a MalformedCorpusError and returned, along with the most recent
976 // error.
977 func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
978         files, err := os.ReadDir(dir)
979         if os.IsNotExist(err) {
980                 return nil, nil // No corpus to read
981         } else if err != nil {
982                 return nil, fmt.Errorf("reading seed corpus from testdata: %v", err)
983         }
984         var corpus []CorpusEntry
985         var errs []error
986         for _, file := range files {
987                 // TODO(jayconrod,katiehockman): determine when a file is a fuzzing input
988                 // based on its name. We should only read files created by writeToCorpus.
989                 // If we read ALL files, we won't be able to change the file format by
990                 // changing the extension. We also won't be able to add files like
991                 // README.txt explaining why the directory exists.
992                 if file.IsDir() {
993                         continue
994                 }
995                 filename := filepath.Join(dir, file.Name())
996                 data, err := os.ReadFile(filename)
997                 if err != nil {
998                         return nil, fmt.Errorf("failed to read corpus file: %v", err)
999                 }
1000                 var vals []any
1001                 vals, err = readCorpusData(data, types)
1002                 if err != nil {
1003                         errs = append(errs, fmt.Errorf("%q: %v", filename, err))
1004                         continue
1005                 }
1006                 corpus = append(corpus, CorpusEntry{Path: filename, Values: vals})
1007         }
1008         if len(errs) > 0 {
1009                 return corpus, &MalformedCorpusError{errs: errs}
1010         }
1011         return corpus, nil
1012 }
1013
1014 func readCorpusData(data []byte, types []reflect.Type) ([]any, error) {
1015         vals, err := unmarshalCorpusFile(data)
1016         if err != nil {
1017                 return nil, fmt.Errorf("unmarshal: %v", err)
1018         }
1019         if err = CheckCorpus(vals, types); err != nil {
1020                 return nil, err
1021         }
1022         return vals, nil
1023 }
1024
1025 // CheckCorpus verifies that the types in vals match the expected types
1026 // provided.
1027 func CheckCorpus(vals []any, types []reflect.Type) error {
1028         if len(vals) != len(types) {
1029                 return fmt.Errorf("wrong number of values in corpus entry: %d, want %d", len(vals), len(types))
1030         }
1031         valsT := make([]reflect.Type, len(vals))
1032         for valsI, v := range vals {
1033                 valsT[valsI] = reflect.TypeOf(v)
1034         }
1035         for i := range types {
1036                 if valsT[i] != types[i] {
1037                         return fmt.Errorf("mismatched types in corpus entry: %v, want %v", valsT, types)
1038                 }
1039         }
1040         return nil
1041 }
1042
1043 // writeToCorpus atomically writes the given bytes to a new file in testdata. If
1044 // the directory does not exist, it will create one. If the file already exists,
1045 // writeToCorpus will not rewrite it. writeToCorpus sets entry.Path to the new
1046 // file that was just written or an error if it failed.
1047 func writeToCorpus(entry *CorpusEntry, dir string) (err error) {
1048         sum := fmt.Sprintf("%x", sha256.Sum256(entry.Data))[:16]
1049         entry.Path = filepath.Join(dir, sum)
1050         if err := os.MkdirAll(dir, 0777); err != nil {
1051                 return err
1052         }
1053         if err := os.WriteFile(entry.Path, entry.Data, 0666); err != nil {
1054                 os.Remove(entry.Path) // remove partially written file
1055                 return err
1056         }
1057         return nil
1058 }
1059
1060 func testName(path string) string {
1061         return filepath.Base(path)
1062 }
1063
1064 func zeroValue(t reflect.Type) any {
1065         for _, v := range zeroVals {
1066                 if reflect.TypeOf(v) == t {
1067                         return v
1068                 }
1069         }
1070         panic(fmt.Sprintf("unsupported type: %v", t))
1071 }
1072
1073 var zeroVals []any = []any{
1074         []byte(""),
1075         string(""),
1076         false,
1077         byte(0),
1078         rune(0),
1079         float32(0),
1080         float64(0),
1081         int(0),
1082         int8(0),
1083         int16(0),
1084         int32(0),
1085         int64(0),
1086         uint(0),
1087         uint8(0),
1088         uint16(0),
1089         uint32(0),
1090         uint64(0),
1091 }
1092
1093 var debugInfo = godebug.New("fuzzdebug").Value() == "1"
1094
1095 func shouldPrintDebugInfo() bool {
1096         return debugInfo
1097 }
1098
1099 func (c *coordinator) debugLogf(format string, args ...any) {
1100         t := time.Now().Format("2006-01-02 15:04:05.999999999")
1101         fmt.Fprintf(c.opts.Log, t+" DEBUG "+format+"\n", args...)
1102 }