]> Cypherpunks repositories - gostls13.git/commitdiff
internal/fuzz: set timeout for each exec of fuzz target
authorKatie Hockman <katie@golang.org>
Wed, 10 Nov 2021 21:22:08 +0000 (16:22 -0500)
committerKatie Hockman <katie@golang.org>
Fri, 12 Nov 2021 18:48:59 +0000 (18:48 +0000)
This change sets a timeout of 10 seconds on each
execution of the fuzz target, both during fuzzing
and during minimization. This is not currently
customizable by the user, but issue #48157 tracks
this work.

Deadlocks will be considered non-recoverable errors,
and as such, will not be minimizable.

Fixes #48591

Change-Id: Ic86e8e9e9a0255e7860f7cbf5654e832785d1cbc
Reviewed-on: https://go-review.googlesource.com/c/go/+/363134
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Katie Hockman <katie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
src/cmd/go/testdata/script/test_fuzz_minimize.txt
src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt
src/cmd/go/testdata/script/test_fuzz_mutator_repeat.txt
src/cmd/go/testdata/script/test_fuzz_non_crash_signal.txt
src/internal/fuzz/minimize_test.go
src/internal/fuzz/worker.go
src/internal/fuzz/worker_test.go

index 462fb9a963e78f193f614240f968197bcb014cb9..a6dc3f19534e8d83ed00845a7c3b52176347ef14 100644 (file)
@@ -67,7 +67,7 @@ rm testdata
 ! go test -fuzz=FuzzMinimizerNonrecoverable -run=FuzzMinimizerNonrecoverable -fuzztime=10000x .
 ! stdout '^ok'
 ! stdout 'minimizing'
-stdout -count=1 'fuzzing process terminated unexpectedly: exit status 99'
+stdout -count=1 '^\s+fuzzing process hung or terminated unexpectedly: exit status 99'
 stdout FAIL
 
 # Check that re-running the value causes a crash.
index 4c4fa8e6514107afcbd62a7b83a13e729eb70ce1..99bae1daf07a1d09cdc4b95bdd8ba24091bbbce0 100644 (file)
@@ -54,9 +54,14 @@ go run check_testdata.go FuzzWithFatalf
 
 ! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=100x -fuzzminimizetime=1000x
 stdout 'testdata[/\\]fuzz[/\\]FuzzWithBadExit[/\\]'
-stdout 'unexpectedly'
+stdout '^\s+fuzzing process hung or terminated unexpectedly: exit status'
 go run check_testdata.go FuzzWithBadExit
 
+! go test -run=FuzzDeadlock -fuzz=FuzzDeadlock -fuzztime=100x -fuzzminimizetime=0x
+stdout 'testdata[/\\]fuzz[/\\]FuzzDeadlock[/\\]'
+stdout '^\s+fuzzing process hung or terminated unexpectedly: exit status'
+go run check_testdata.go FuzzDeadlock
+
 # Running the fuzzer should find a crashing input quickly for fuzzing two types.
 ! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=100x -fuzzminimizetime=1000x
 stdout 'testdata[/\\]fuzz[/\\]FuzzWithTwoTypes[/\\]'
@@ -190,6 +195,15 @@ func FuzzWithBadExit(f *testing.F) {
        })
 }
 
+func FuzzDeadlock(f *testing.F) {
+       f.Add(int(0))
+       f.Fuzz(func(t *testing.T, n int) {
+               if n != 0 {
+                       select {}
+               }
+       })
+}
+
 func FuzzWithTwoTypes(f *testing.F) {
        f.Fuzz(func(t *testing.T, a, b []byte) {
                if len(a) > 0 && len(b) > 0 {
index 60f57874648f0287366c3f9724b75c0440256f5b..3764dcb9157a7fd86ac617abd67f8e83c7b90dd7 100644 (file)
@@ -12,7 +12,7 @@
 # The fuzzing engine reconstructs the crashing input and saves it to testdata.
 ! exists want
 ! go test -fuzz=. -parallel=1 -fuzztime=110x -fuzzminimizetime=10x -v
-stdout 'fuzzing process terminated unexpectedly'
+stdout '^\s+fuzzing process hung or terminated unexpectedly: exit status'
 stdout 'Failing input written to testdata'
 
 # Run the fuzz target without fuzzing. The fuzz function is called with the
index 31d54bcb702a17164170edc0250933bf15515ea9..1051292fcb847ee796c4a0608bf5138abe58879b 100644 (file)
@@ -25,7 +25,7 @@ stdout 'fuzzing process terminated by unexpected signal; no crash will be record
 # We should save a crasher.
 ! go test -fuzz=FuzzCrash
 exists testdata/fuzz/FuzzCrash
-stdout 'fuzzing process terminated unexpectedly'
+stdout '^\s+fuzzing process hung or terminated unexpectedly: exit status'
 
 -- go.mod --
 module test
index dc153d0de4f0a46d2e685259e539278d1f59c920..04d785ce402f1a011084f394a26da3b5ac23a12d 100644 (file)
@@ -13,6 +13,7 @@ import (
        "fmt"
        "reflect"
        "testing"
+       "time"
        "unicode"
        "unicode/utf8"
 )
@@ -279,7 +280,9 @@ func TestMinimizeInput(t *testing.T) {
                t.Run(tc.name, func(t *testing.T) {
                        t.Parallel()
                        ws := &workerServer{
-                               fuzzFn: tc.fn,
+                               fuzzFn: func(e CorpusEntry) (time.Duration, error) {
+                                       return time.Second, tc.fn(e)
+                               },
                        }
                        count := int64(0)
                        vals := tc.input
@@ -304,8 +307,8 @@ func TestMinimizeInput(t *testing.T) {
 // input and a flaky failure occurs, that minimization was not indicated
 // to be successful, and the error isn't returned (since it's flaky).
 func TestMinimizeFlaky(t *testing.T) {
-       ws := &workerServer{fuzzFn: func(e CorpusEntry) error {
-               return errors.New("ohno")
+       ws := &workerServer{fuzzFn: func(e CorpusEntry) (time.Duration, error) {
+               return time.Second, errors.New("ohno")
        }}
        keepCoverage := make([]byte, len(coverageSnapshot))
        count := int64(0)
index 02efa7f84a4213cc3b7f082c5ff88ac88d9c997c..48a392311285712d15da2b256747cc3b8892b6f3 100644 (file)
@@ -142,7 +142,7 @@ func (w *worker) coordinate(ctx context.Context) error {
                        }
                        // Worker exited non-zero or was terminated by a non-interrupt
                        // signal (for example, SIGSEGV) while fuzzing.
-                       return fmt.Errorf("fuzzing process terminated unexpectedly: %w", err)
+                       return fmt.Errorf("fuzzing process hung or terminated unexpectedly: %w", err)
                        // TODO(jayconrod,katiehockman): if -keepfuzzing, restart worker.
 
                case input := <-w.coordinator.inputC:
@@ -183,7 +183,7 @@ func (w *worker) coordinate(ctx context.Context) error {
                                // Unexpected termination. Set error message and fall through.
                                // We'll restart the worker on the next iteration.
                                // Don't attempt to minimize this since it crashed the worker.
-                               resp.Err = fmt.Sprintf("fuzzing process terminated unexpectedly: %v", w.waitErr)
+                               resp.Err = fmt.Sprintf("fuzzing process hung or terminated unexpectedly: %v", w.waitErr)
                                canMinimize = false
                        }
                        result := fuzzResult{
@@ -255,7 +255,7 @@ func (w *worker) minimize(ctx context.Context, input fuzzMinimizeInput) (min fuz
                                limit:        input.limit,
                        }, nil
                }
-               return fuzzResult{}, fmt.Errorf("fuzzing process terminated unexpectedly while minimizing: %w", w.waitErr)
+               return fuzzResult{}, fmt.Errorf("fuzzing process hung or terminated unexpectedly while minimizing: %w", w.waitErr)
        }
 
        if input.crasherMsg != "" && resp.Err == "" {
@@ -471,8 +471,16 @@ func RunFuzzWorker(ctx context.Context, fn func(CorpusEntry) error) error {
        }
        srv := &workerServer{
                workerComm: comm,
-               fuzzFn:     fn,
-               m:          newMutator(),
+               fuzzFn: func(e CorpusEntry) (time.Duration, error) {
+                       timer := time.AfterFunc(10*time.Second, func() {
+                               panic("deadlocked!") // this error message won't be printed
+                       })
+                       defer timer.Stop()
+                       start := time.Now()
+                       err := fn(e)
+                       return time.Since(start), err
+               },
+               m: newMutator(),
        }
        return srv.serve(ctx)
 }
@@ -604,9 +612,12 @@ type workerServer struct {
        // coverage is found.
        coverageMask []byte
 
-       // fuzzFn runs the worker's fuzz function on the given input and returns
-       // an error if it finds a crasher (the process may also exit or crash).
-       fuzzFn func(CorpusEntry) error
+       // fuzzFn runs the worker's fuzz target on the given input and returns an
+       // error if it finds a crasher (the process may also exit or crash), and the
+       // time it took to run the input. It sets a deadline of 10 seconds, at which
+       // point it will panic with the assumption that the process is hanging or
+       // deadlocked.
+       fuzzFn func(CorpusEntry) (time.Duration, error)
 }
 
 // serve reads serialized RPC messages on fuzzIn. When serve receives a message,
@@ -699,9 +710,8 @@ func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) (resp fuzzRespo
        }
        fuzzOnce := func(entry CorpusEntry) (dur time.Duration, cov []byte, errMsg string) {
                mem.header().count++
-               start := time.Now()
-               err := ws.fuzzFn(entry)
-               dur = time.Since(start)
+               var err error
+               dur, err = ws.fuzzFn(entry)
                if err != nil {
                        errMsg = err.Error()
                        if errMsg == "" {
@@ -803,7 +813,7 @@ func (ws *workerServer) minimizeInput(ctx context.Context, vals []interface{}, c
        // If not, then whatever caused us to think the value was interesting may
        // have been a flake, and we can't minimize it.
        *count++
-       retErr = ws.fuzzFn(CorpusEntry{Values: vals})
+       _, retErr = ws.fuzzFn(CorpusEntry{Values: vals})
        if keepCoverage != nil {
                if !hasCoverageBit(keepCoverage, coverageSnapshot) || retErr != nil {
                        return false, nil
@@ -870,7 +880,7 @@ func (ws *workerServer) minimizeInput(ctx context.Context, vals []interface{}, c
                        panic("impossible")
                }
                *count++
-               err := ws.fuzzFn(CorpusEntry{Values: vals})
+               _, err := ws.fuzzFn(CorpusEntry{Values: vals})
                if err != nil {
                        retErr = err
                        if keepCoverage != nil {
index c6f83fd08dd96cd372d49d2d393c2bbbca5e4773..ed9722f43ab1179a66ccae232d6bf019db30c3d1 100644 (file)
@@ -14,6 +14,7 @@ import (
        "os/signal"
        "reflect"
        "testing"
+       "time"
 )
 
 var benchmarkWorkerFlag = flag.Bool("benchmarkworker", false, "")
@@ -36,7 +37,7 @@ func BenchmarkWorkerFuzzOverhead(b *testing.B) {
        os.Setenv("GODEBUG", fmt.Sprintf("%s,fuzzseed=123", origEnv))
 
        ws := &workerServer{
-               fuzzFn:     func(_ CorpusEntry) error { return nil },
+               fuzzFn:     func(_ CorpusEntry) (time.Duration, error) { return time.Second, nil },
                workerComm: workerComm{memMu: make(chan *sharedMem, 1)},
        }