diff options
| author | Jay Conrod <jayconrod@google.com> | 2020-10-02 16:05:33 -0400 |
|---|---|---|
| committer | Filippo Valsorda <filippo@golang.org> | 2020-12-04 19:17:29 +0100 |
| commit | 8fabdcee8ff0537097ae68619ff515563bb2f986 (patch) | |
| tree | 6e5ea2e5b8392f5c2d592a94440b6fa1e7489b1a /src/internal/fuzz/fuzz.go | |
| parent | 0a6f004cb1ed99bc225f4fe3cba5c2c5b901b442 (diff) | |
| download | go-8fabdcee8ff0537097ae68619ff515563bb2f986.tar.xz | |
[dev.fuzz] internal/fuzz: coordinate fuzzing across workers
Package fuzz provides common fuzzing functionality for tests built
with "go test" and for programs that use fuzzing functionality in the
testing package.
Change-Id: I3901c6a993a9adb8a93733ae1838b86dd78c7036
Reviewed-on: https://go-review.googlesource.com/c/go/+/259259
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
Trust: Katie Hockman <katie@golang.org>
Trust: Jay Conrod <jayconrod@google.com>
Diffstat (limited to 'src/internal/fuzz/fuzz.go')
| -rw-r--r-- | src/internal/fuzz/fuzz.go | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/src/internal/fuzz/fuzz.go b/src/internal/fuzz/fuzz.go new file mode 100644 index 0000000000..4f1d204834 --- /dev/null +++ b/src/internal/fuzz/fuzz.go @@ -0,0 +1,143 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fuzz provides common fuzzing functionality for tests built with +// "go test" and for programs that use fuzzing functionality in the testing +// package. +package fuzz + +import ( + "os" + "runtime" + "sync" + "time" +) + +// CoordinateFuzzing creates several worker processes and communicates with +// them to test random inputs that could trigger crashes and expose bugs. +// The worker processes run the same binary in the same directory with the +// same environment variables as the coordinator process. Workers also run +// with the same arguments as the coordinator, except with the -test.fuzzworker +// flag prepended to the argument list. +// +// parallel is the number of worker processes to run in parallel. If parallel +// is 0, CoordinateFuzzing will run GOMAXPROCS workers. +// +// seed is a list of seed values added by the fuzz target with testing.F.Add. +// Seed values from testdata and GOFUZZCACHE should not be included in this +// list; this function loads them separately. +func CoordinateFuzzing(parallel int, seed [][]byte) error { + if parallel == 0 { + parallel = runtime.GOMAXPROCS(0) + } + // TODO(jayconrod): support fuzzing indefinitely or with a given duration. + // The value below is just a placeholder until we figure out how to handle + // interrupts. + duration := 5 * time.Second + + // TODO(jayconrod): do we want to support fuzzing different binaries? + dir := "" // same as self + binPath := os.Args[0] + args := append([]string{"-test.fuzzworker"}, os.Args[1:]...) + env := os.Environ() // same as self + + c := &coordinator{ + doneC: make(chan struct{}), + inputC: make(chan corpusEntry), + } + + newWorker := func() *worker { + return &worker{ + dir: dir, + binPath: binPath, + args: args, + env: env, + coordinator: c, + } + } + + corpus := corpus{entries: make([]corpusEntry, len(seed))} + for i, v := range seed { + corpus.entries[i].b = v + } + if len(corpus.entries) == 0 { + // TODO(jayconrod,katiehockman): pick a good starting corpus when one is + // missing or very small. + corpus.entries = append(corpus.entries, corpusEntry{b: []byte{0}}) + } + + // TODO(jayconrod,katiehockman): read corpus from testdata. + // TODO(jayconrod,katiehockman): read corpus from GOFUZZCACHE. + + // Start workers. + workers := make([]*worker, parallel) + runErrs := make([]error, parallel) + var wg sync.WaitGroup + wg.Add(parallel) + for i := 0; i < parallel; i++ { + go func(i int) { + defer wg.Done() + workers[i] = newWorker() + runErrs[i] = workers[i].runFuzzing() + }(i) + } + + // Main event loop. + stopC := time.After(duration) + i := 0 + for { + select { + // TODO(jayconrod): handle interruptions like SIGINT. + // TODO(jayconrod,katiehockman): receive crashers and new corpus values + // from workers. + + case <-stopC: + // Time's up. + close(c.doneC) + + case <-c.doneC: + // Wait for workers to stop and return. + wg.Wait() + for _, err := range runErrs { + if err != nil { + return err + } + } + return nil + + case c.inputC <- corpus.entries[i]: + // Sent the next input to any worker. + // TODO(jayconrod,katiehockman): need a scheduling algorithm that chooses + // which corpus value to send next (or generates something new). + i = (i + 1) % len(corpus.entries) + } + } + + // TODO(jayconrod,katiehockman): write crashers to testdata and other inputs + // to GOFUZZCACHE. If the testdata directory is outside the current module, + // always write to GOFUZZCACHE, since the testdata is likely read-only. +} + +type corpus struct { + entries []corpusEntry +} + +// TODO(jayconrod,katiehockman): decide whether and how to unify this type +// with the equivalent in testing. +type corpusEntry struct { + b []byte +} + +// coordinator holds channels that workers can use to communicate with +// the coordinator. +type coordinator struct { + // doneC is closed to indicate fuzzing is done and workers should stop. + // doneC may be closed due to a time limit expiring or a fatal error in + // a worker. + doneC chan struct{} + + // inputC is sent values to fuzz by the coordinator. Any worker may receive + // values from this channel. + inputC chan corpusEntry +} |
