...

Text file src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt

Documentation: cmd/go/testdata/script

     1[short] skip
     2[!fuzz-instrumented] skip
     3
     4# Test that when an interesting value is discovered (one that expands coverage),
     5# the fuzzing engine minimizes it before writing it to the cache.
     6#
     7# The program below starts with a seed value of length 100, but more coverage
     8# will be found for any value other than the seed. We should end with a value
     9# in the cache of length 1 (the minimizer currently does not produce empty
    10# strings). check_cache.go confirms that.
    11#
    12# We would like to verify that ALL values in the cache were minimized to a
    13# length of 1, but this isn't always possible when new coverage is found in
    14# functions called by testing or internal/fuzz in the background.
    15
    16go test -c -fuzz=.  # Build using shared build cache for speed.
    17env GOCACHE=$WORK/gocache
    18exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinCache -test.fuzztime=1000x
    19go run check_cache/check_cache.go $GOCACHE/fuzz/FuzzMinCache
    20
    21# Test that minimization occurs for a crash that appears while minimizing a
    22# newly found interesting input. There must be only one worker for this test to
    23# be flaky like we want.
    24! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.run=^$ -test.fuzztime=10000x -test.parallel=1
    25! stdout '^ok'
    26stdout -count=1 'got the minimum size!'
    27stdout -count=1 'bad input'
    28stdout FAIL
    29# Check that the input written to testdata will reproduce the error, and is the
    30# smallest possible.
    31go run check_testdata/check_testdata.go FuzzMinimizerCrashInMinimization 1
    32
    33# Test that a nonrecoverable error that occurs while minimizing an interesting
    34# input is reported correctly.
    35! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerNonrecoverableCrashInMinimization -test.run=^$ -test.fuzztime=10000x -test.parallel=1
    36! stdout '^ok'
    37stdout -count=1 'fuzzing process hung or terminated unexpectedly while minimizing'
    38stdout -count=1 'EOF'
    39stdout FAIL
    40# Check that the input written to testdata will reproduce the error.
    41go run check_testdata/check_testdata.go FuzzMinimizerNonrecoverableCrashInMinimization 1
    42
    43-- go.mod --
    44module fuzz
    45
    46go 1.17
    47-- y.go --
    48package fuzz
    49
    50import (
    51	"bytes"
    52	"io"
    53)
    54
    55func Y(w io.Writer, s string) {
    56	if !bytes.Equal([]byte(s), []byte("y")) {
    57		w.Write([]byte("not equal"))
    58	}
    59}
    60-- fuzz_test.go --
    61package fuzz
    62
    63import (
    64	"bytes"
    65	"os"
    66	"testing"
    67)
    68
    69func FuzzMinimizerCrashInMinimization(f *testing.F) {
    70	seed := bytes.Repeat([]byte{255}, 100)
    71	f.Add(seed)
    72	f.Fuzz(func(t *testing.T, b []byte) {
    73		if bytes.Equal(seed, b) {
    74			return
    75		}
    76		t.Error("bad input")
    77		if len(b) == 1 {
    78			t.Error("got the minimum size!")
    79		}
    80	})
    81}
    82
    83var fuzzing bool
    84
    85func FuzzMinimizerNonrecoverableCrashInMinimization(f *testing.F) {
    86	seed := bytes.Repeat([]byte{255}, 100)
    87	f.Add(seed)
    88	f.Fuzz(func(t *testing.T, b []byte) {
    89		if bytes.Equal(seed, b) {
    90			return
    91		} else if len(b) == 1 {
    92			os.Exit(1)
    93		}
    94	})
    95}
    96
    97func FuzzMinCache(f *testing.F) {
    98	seed := bytes.Repeat([]byte("a"), 20)
    99	f.Add(seed)
   100	f.Fuzz(func(t *testing.T, buf []byte) {
   101		if bytes.Equal(buf, seed) {
   102			return
   103		}
   104	})
   105}
   106-- check_testdata/check_testdata.go --
   107//go:build ignore
   108// +build ignore
   109
   110// check_testdata.go checks that the string written
   111// is not longer than the provided length.
   112package main
   113
   114import (
   115	"bytes"
   116	"fmt"
   117	"io/ioutil"
   118	"os"
   119	"path/filepath"
   120	"regexp"
   121	"strconv"
   122)
   123
   124func main() {
   125	wantLen, err := strconv.Atoi(os.Args[2])
   126	if err != nil {
   127		fmt.Fprintln(os.Stderr, err)
   128		os.Exit(1)
   129	}
   130	testName := os.Args[1]
   131	dir := filepath.Join("testdata/fuzz", testName)
   132
   133	files, err := ioutil.ReadDir(dir)
   134	if err != nil {
   135		fmt.Fprintln(os.Stderr, err)
   136		os.Exit(1)
   137	}
   138
   139	if len(files) == 0 {
   140		fmt.Fprintf(os.Stderr, "expect at least one failure to be written to testdata\n")
   141		os.Exit(1)
   142	}
   143
   144	for _, f := range files {
   145		data, err := ioutil.ReadFile(filepath.Join(dir, f.Name()))
   146		if err != nil {
   147			panic(err)
   148		}
   149		var containsVal bool
   150		for _, line := range bytes.Split(data, []byte("\n")) {
   151			m := valRe.FindSubmatch(line)
   152			if m == nil {
   153				continue
   154			}
   155			containsVal = true
   156			s, err := strconv.Unquote(string(m[1]))
   157			if err != nil {
   158				panic(err)
   159			}
   160			if len(s) != wantLen {
   161				fmt.Fprintf(os.Stderr, "expect length %d, got %d (%q)\n", wantLen, len(s), line)
   162				os.Exit(1)
   163			}
   164		}
   165		if !containsVal {
   166			fmt.Fprintln(os.Stderr, "corpus file contained no values")
   167			os.Exit(1)
   168		}
   169	}
   170}
   171
   172var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`)
   173
   174-- check_cache/check_cache.go --
   175//go:build ignore
   176// +build ignore
   177
   178// check_cache.go checks that each file in the cached corpus has a []byte
   179// of length at most 1. This verifies that at least one cached input is minimized.
   180package main
   181
   182import (
   183	"bytes"
   184	"fmt"
   185	"os"
   186	"path/filepath"
   187	"regexp"
   188	"strconv"
   189)
   190
   191func main() {
   192	dir := os.Args[1]
   193	ents, err := os.ReadDir(dir)
   194	if err != nil {
   195		fmt.Fprintln(os.Stderr, err)
   196		os.Exit(1)
   197	}
   198	for _, ent := range ents {
   199		name := filepath.Join(dir, ent.Name())
   200		if good, err := checkCacheFile(name); err != nil {
   201			fmt.Fprintln(os.Stderr, err)
   202			os.Exit(1)
   203		} else if good {
   204			os.Exit(0)
   205		}
   206	}
   207	fmt.Fprintln(os.Stderr, "no cached inputs were minimized")
   208	os.Exit(1)
   209}
   210
   211func checkCacheFile(name string) (good bool, err error) {
   212	data, err := os.ReadFile(name)
   213	if err != nil {
   214		return false, err
   215	}
   216	for _, line := range bytes.Split(data, []byte("\n")) {
   217		m := valRe.FindSubmatch(line)
   218		if m == nil {
   219			continue
   220		}
   221		if s, err := strconv.Unquote(string(m[1])); err != nil {
   222			return false, err
   223		} else if len(s) <= 1 {
   224			return true, nil
   225		}
   226	}
   227	return false, nil
   228}
   229
   230var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`)

View as plain text