| [short] skip |
| [!fuzz-instrumented] skip |
| |
| # Test that when an interesting value is discovered (one that expands coverage), |
| # the fuzzing engine minimizes it before writing it to the cache. |
| # |
| # The program below starts with a seed value of length 100, but more coverage |
| # will be found for any value other than the seed. We should end with a value |
| # in the cache of length 1 (the minimizer currently does not produce empty |
| # strings). check_cache.go confirms that. |
| # |
| # We would like to verify that ALL values in the cache were minimized to a |
| # length of 1, but this isn't always possible when new coverage is found in |
| # functions called by testing or internal/fuzz in the background. |
| |
| go test -c -fuzz=. # Build using shared build cache for speed. |
| env GOCACHE=$WORK/gocache |
| exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinCache -test.fuzztime=1000x |
| go run check_cache/check_cache.go $GOCACHE/fuzz/FuzzMinCache |
| |
| go test -c -fuzz=. # Build using shared build cache for speed. |
| env GOCACHE=$WORK/gocache |
| |
| # Test that minimization occurs for a crash that appears while minimizing a |
| # newly found interesting input. There must be only one worker for this test to |
| # be flaky like we want. |
| ! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.run=FuzzMinimizerCrashInMinimization -test.fuzztime=10000x -test.parallel=1 |
| ! stdout '^ok' |
| stdout -count=1 'got the minimum size!' |
| stdout -count=1 'flaky failure' |
| stdout FAIL |
| # Check that the input written to testdata will reproduce the error, and is the |
| # smallest possible. |
| go run check_testdata/check_testdata.go FuzzMinimizerCrashInMinimization 50 |
| |
| # Test that a nonrecoverable error that occurs while minimizing an interesting |
| # input is reported correctly. |
| ! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerNonrecoverableCrashInMinimization -test.run=FuzzMinimizerNonrecoverableCrashInMinimization -test.fuzztime=10000x -test.parallel=1 |
| ! stdout '^ok' |
| stdout -count=1 'fuzzing process hung or terminated unexpectedly while minimizing' |
| stdout -count=1 'EOF' |
| stdout FAIL |
| # Check that the input written to testdata will reproduce the error. |
| go run check_testdata/check_testdata.go FuzzMinimizerNonrecoverableCrashInMinimization 100 |
| |
| -- go.mod -- |
| module fuzz |
| |
| go 1.17 |
| -- y.go -- |
| package fuzz |
| |
| import ( |
| "bytes" |
| "io" |
| ) |
| |
| func Y(w io.Writer, s string) { |
| if !bytes.Equal([]byte(s), []byte("y")) { |
| w.Write([]byte("not equal")) |
| } |
| } |
| -- fuzz_test.go -- |
| package fuzz |
| |
| import ( |
| "bytes" |
| "io" |
| "os" |
| "strings" |
| "testing" |
| "unicode/utf8" |
| ) |
| |
| func FuzzMinimizerCrashInMinimization(f *testing.F) { |
| seed := strings.Repeat("A", 1000) |
| f.Add(seed) |
| i := 3 |
| f.Fuzz(func(t *testing.T, s string) { |
| if len(s) < 50 || len(s) > 1100 { |
| // Make sure that b is large enough that it can be minimized |
| return |
| } |
| if s != seed { |
| // This should hit a new edge, and the interesting input |
| // should attempt minimization |
| Y(io.Discard, s) |
| } |
| if i > 0 { |
| // Don't let it fail right away. |
| i-- |
| } else if utf8.RuneCountInString(s) == len(s) && len(s) <= 100 { |
| // Make sure this only fails if the number of bytes in the |
| // marshaled string is the same as the unmarshaled string, |
| // so that we can check the length of the testdata file. |
| t.Error("flaky failure") |
| if len(s) == 50 { |
| t.Error("got the minimum size!") |
| } |
| } |
| }) |
| } |
| |
| func FuzzMinimizerNonrecoverableCrashInMinimization(f *testing.F) { |
| seed := strings.Repeat("A", 1000) |
| f.Add(seed) |
| i := 3 |
| f.Fuzz(func(t *testing.T, s string) { |
| if len(s) < 50 || len(s) > 1100 { |
| return |
| } |
| if s != seed { |
| Y(io.Discard, s) |
| } |
| if i > 0 { |
| i-- |
| } else if utf8.RuneCountInString(s) == len(s) && len(s) <= 100 { |
| os.Exit(19) |
| } |
| }) |
| } |
| |
| func FuzzMinCache(f *testing.F) { |
| seed := bytes.Repeat([]byte("a"), 20) |
| f.Add(seed) |
| f.Fuzz(func(t *testing.T, buf []byte) { |
| if bytes.Equal(buf, seed) { |
| return |
| } |
| if n := sum(buf); n < 0 { |
| t.Error("sum cannot be negative") |
| } |
| }) |
| } |
| |
| func sum(buf []byte) int { |
| n := 0 |
| for _, b := range buf { |
| n += int(b) |
| } |
| return n |
| } |
| -- check_testdata/check_testdata.go -- |
| //go:build ignore |
| // +build ignore |
| |
| // check_testdata.go checks that the string written |
| // is not longer than the provided length. |
| package main |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strconv" |
| ) |
| |
| func main() { |
| wantLen, err := strconv.Atoi(os.Args[2]) |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| testName := os.Args[1] |
| dir := filepath.Join("testdata/fuzz", testName) |
| |
| files, err := ioutil.ReadDir(dir) |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| |
| if len(files) == 0 { |
| fmt.Fprintf(os.Stderr, "expect at least one failure to be written to testdata\n") |
| os.Exit(1) |
| } |
| |
| fname := files[0].Name() |
| contents, err := ioutil.ReadFile(filepath.Join(dir, fname)) |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| contentsLen := len(contents) - len(`go test fuzz v1 |
| string("") |
| `) |
| if got, want := contentsLen, wantLen; got > want { |
| fmt.Fprintf(os.Stderr, "expect length <= %d, got %d\n", want, got) |
| os.Exit(1) |
| } |
| fmt.Fprintf(os.Stderr, "%s\n", contents) |
| } |
| |
| -- check_cache/check_cache.go -- |
| //go:build ignore |
| // +build ignore |
| |
| // check_cache.go checks that each file in the cached corpus has a []byte |
| // of length at most 1. This verifies that at least one cached input is minimized. |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strconv" |
| ) |
| |
| func main() { |
| dir := os.Args[1] |
| ents, err := os.ReadDir(dir) |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| for _, ent := range ents { |
| name := filepath.Join(dir, ent.Name()) |
| if good, err := checkCacheFile(name); err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } else if good { |
| os.Exit(0) |
| } |
| } |
| fmt.Fprintln(os.Stderr, "no cached inputs were minimized") |
| os.Exit(1) |
| } |
| |
| func checkCacheFile(name string) (good bool, err error) { |
| data, err := os.ReadFile(name) |
| if err != nil { |
| return false, err |
| } |
| for _, line := range bytes.Split(data, []byte("\n")) { |
| m := valRe.FindSubmatch(line) |
| if m == nil { |
| continue |
| } |
| if s, err := strconv.Unquote(string(m[1])); err != nil { |
| return false, err |
| } else if len(s) <= 1 { |
| return true, nil |
| } |
| } |
| return false, nil |
| } |
| |
| var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`) |