From: Katie Hockman Date: Tue, 6 Apr 2021 19:33:55 +0000 (-0400) Subject: [dev.fuzz] internal/fuzz: small bug fixes and refactors to minimization X-Git-Tag: go1.18beta1~1282^2~78 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=161439fec01692d4111fd4bd0eb0d3416ec8d594;p=gostls13.git [dev.fuzz] internal/fuzz: small bug fixes and refactors to minimization This fixes a few issues that were being masked since log statements weren't being printed to stdout. Now that they are, fix the bugs, and update the tests. Also includes a few small refactors which will make minimizing non-recoverable errors easier. Change-Id: Ie2fd2e5534b3980317e1e1f3fd8e04750988c17f Reviewed-on: https://go-review.googlesource.com/c/go/+/307810 Trust: Katie Hockman Run-TryBot: Katie Hockman TryBot-Result: Go Bot Reviewed-by: Jay Conrod --- diff --git a/src/cmd/go/testdata/script/test_fuzz_mutator.txt b/src/cmd/go/testdata/script/test_fuzz_mutator.txt index c5a7a86b84..8ec73bf35e 100644 --- a/src/cmd/go/testdata/script/test_fuzz_mutator.txt +++ b/src/cmd/go/testdata/script/test_fuzz_mutator.txt @@ -19,15 +19,15 @@ go run check_logs.go fuzz fuzz.worker stdout FAIL stdout 'mutator found enough unique mutations' -# Test that minimization is working. -! go test -fuzz=FuzzMinimizer -run=FuzzMinimizer -parallel=1 -fuzztime=5s minimizer_test.go -! stdout ok -# TODO(jayconrod,katiehockman): Once logging is fixed, add this test in. -# stdout 'got the minimum size!' +# Test that minimization is working for recoverable errors. +! go test -v -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -parallel=1 -fuzztime=10s minimizer_test.go +! stdout '^ok' +stdout 'got the minimum size!' +stdout 'contains a letter' stdout FAIL # Test that re-running the minimized value causes a crash. -! go test -run=FuzzMinimizer minimizer_test.go +! go test -run=FuzzMinimizerRecoverable minimizer_test.go -- go.mod -- module m @@ -85,7 +85,7 @@ import ( "testing" ) -func FuzzMinimizer(f *testing.F) { +func FuzzMinimizerRecoverable(f *testing.F) { f.Fuzz(func(t *testing.T, b []byte) { if len(b) < 100 { // Make sure that b is large enough that it can be minimized diff --git a/src/internal/fuzz/worker.go b/src/internal/fuzz/worker.go index 243a12baef..506a485f24 100644 --- a/src/internal/fuzz/worker.go +++ b/src/internal/fuzz/worker.go @@ -515,9 +515,12 @@ func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) fuzzResponse { ws.m.mutate(vals, cap(mem.valueRef())) writeToMem(vals, mem) if err := ws.fuzzFn(CorpusEntry{Values: vals}); err != nil { - if minErr := ws.minimize(ctx, vals, mem); minErr != nil { + // TODO(jayconrod,katiehockman): consider making the maximum minimization + // time customizable with a go command flag. + minCtx, minCancel := context.WithTimeout(ctx, time.Minute) + defer minCancel() + if minErr := ws.minimize(minCtx, vals, mem); minErr != nil { // Minimization found a different error, so use that one. - writeToMem(vals, mem) err = minErr } return fuzzResponse{Crashed: true, Err: err.Error()} @@ -528,22 +531,20 @@ func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) fuzzResponse { } } -// minimizeInput applies a series of minimizing transformations on the provided +// minimize applies a series of minimizing transformations on the provided // vals, ensuring that each minimization still causes an error in fuzzFn. Before // every call to fuzzFn, it marshals the new vals and writes it to the provided // mem just in case an unrecoverable error occurs. It runs for a maximum of one // minute, and returns the last error it found. -func (ws *workerServer) minimize(ctx context.Context, vals []interface{}, mem *sharedMem) error { - // TODO(jayconrod,katiehockman): consider making the maximum minimization - // time customizable with a go command flag. - ctx, cancel := context.WithTimeout(ctx, time.Minute) - defer cancel() - var retErr error +func (ws *workerServer) minimize(ctx context.Context, vals []interface{}, mem *sharedMem) (retErr error) { + // Make sure the last crashing value is written to mem. + defer writeToMem(vals, mem) // tryMinimized will run the fuzz function for the values in vals at the // time the function is called. If err is nil, then the minimization was // unsuccessful, since we expect an error to still occur. tryMinimized := func(i int, prevVal interface{}) error { + writeToMem(vals, mem) // write to mem in case a non-recoverable crash occurs err := ws.fuzzFn(CorpusEntry{Values: vals}) if err == nil { // The fuzz function succeeded, so return the value at index i back @@ -565,11 +566,11 @@ func (ws *workerServer) minimize(ctx context.Context, vals []interface{}, mem *s // First, try to cut the tail. for n := 1024; n != 0; n /= 2 { for len(v) > n { - if ctx.Done() != nil { + if ctx.Err() != nil { return retErr } vals[valI] = v[:len(v)-n] - if tryMinimized(valI, v) != nil { + if tryMinimized(valI, v) == nil { break } // Set v to the new value to continue iterating. @@ -580,14 +581,14 @@ func (ws *workerServer) minimize(ctx context.Context, vals []interface{}, mem *s // Then, try to remove each individual byte. tmp := make([]byte, len(v)) for i := 0; i < len(v)-1; i++ { - if ctx.Done() != nil { + if ctx.Err() != nil { return retErr } candidate := tmp[:len(v)-1] copy(candidate[:i], v[:i]) copy(candidate[i:], v[i+1:]) vals[valI] = candidate - if tryMinimized(valI, v) != nil { + if tryMinimized(valI, v) == nil { continue } // Update v to delete the value at index i. @@ -602,13 +603,13 @@ func (ws *workerServer) minimize(ctx context.Context, vals []interface{}, mem *s for i := 0; i < len(v)-1; i++ { copy(tmp, v[:i]) for j := len(v); j > i+1; j-- { - if ctx.Done() != nil { + if ctx.Err() != nil { return retErr } candidate := tmp[:len(v)-j+i] copy(candidate[i:], v[j:]) vals[valI] = candidate - if tryMinimized(valI, v) != nil { + if tryMinimized(valI, v) == nil { continue } // Update v and reset the loop with the new length.