summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
blob: e61c4f9d04eb9d3c8cee10d6ce3c073c82bb260c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
[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=XXX -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=XXX -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\(([^)]+)\)$`)