aboutsummaryrefslogtreecommitdiff
path: root/src/internal/fuzz/fuzz.go
diff options
context:
space:
mode:
authorJay Conrod <jayconrod@google.com>2020-10-02 16:05:33 -0400
committerFilippo Valsorda <filippo@golang.org>2020-12-04 19:17:29 +0100
commit8fabdcee8ff0537097ae68619ff515563bb2f986 (patch)
tree6e5ea2e5b8392f5c2d592a94440b6fa1e7489b1a /src/internal/fuzz/fuzz.go
parent0a6f004cb1ed99bc225f4fe3cba5c2c5b901b442 (diff)
downloadgo-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.go143
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
+}