blob: 11aaacaad26b07fba8ce0d9a7e7dbbe4f07e6afc [file] [log] [blame]
[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
# 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=^$ -test.fuzztime=10000x -test.parallel=1
! stdout '^ok'
stdout -count=1 'got the minimum size!'
stdout -count=1 'bad input'
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 1
# 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=^$ -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 1
-- 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"
"os"
"testing"
)
func FuzzMinimizerCrashInMinimization(f *testing.F) {
seed := bytes.Repeat([]byte{255}, 100)
f.Add(seed)
f.Fuzz(func(t *testing.T, b []byte) {
if bytes.Equal(seed, b) {
return
}
t.Error("bad input")
if len(b) == 1 {
t.Error("got the minimum size!")
}
})
}
var fuzzing bool
func FuzzMinimizerNonrecoverableCrashInMinimization(f *testing.F) {
seed := bytes.Repeat([]byte{255}, 100)
f.Add(seed)
f.Fuzz(func(t *testing.T, b []byte) {
if bytes.Equal(seed, b) {
return
} else if len(b) == 1 {
os.Exit(1)
}
})
}
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
}
})
}
-- 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 (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"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)
}
for _, f := range files {
data, err := ioutil.ReadFile(filepath.Join(dir, f.Name()))
if err != nil {
panic(err)
}
var containsVal bool
for _, line := range bytes.Split(data, []byte("\n")) {
m := valRe.FindSubmatch(line)
if m == nil {
continue
}
containsVal = true
s, err := strconv.Unquote(string(m[1]))
if err != nil {
panic(err)
}
if len(s) != wantLen {
fmt.Fprintf(os.Stderr, "expect length %d, got %d (%q)\n", wantLen, len(s), line)
os.Exit(1)
}
}
if !containsVal {
fmt.Fprintln(os.Stderr, "corpus file contained no values")
os.Exit(1)
}
}
}
var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`)
-- 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\(([^)]+)\)$`)