go test -fuzz=FuzzDeadline -timeout=0 -fuzztime=100x -wantdeadline=false
-- go.mod --
-module fzz
+module fuzz
go 1.16
-- fuzz_deadline_test.go --
# There are no seed values, so 'go test' should finish quickly.
go test
-# Fuzzing should exit 0 when after fuzztime, even if timeout is short.
+# Fuzzing should exit 0 after fuzztime, even if timeout is short.
go test -timeout=10ms -fuzz=FuzzFast -fuzztime=5s
# We should see the same behavior when invoking the test binary directly.
# We count the files to find the number of runs.
mkdir count
env GOCACHE=$WORK/tmp
-go test -fuzz=FuzzCount -fuzztime=1000x
-go run count_files.go
-stdout '^1000$'
+go test -fuzz=FuzzCount -fuzztime=1000x -fuzzminimizetime=1x
+go run check_file_count.go 1000
-- go.mod --
module fuzz
n++
})
}
--- count_files.go --
+-- check_file_count.go --
// +build ignore
package main
import (
"fmt"
"os"
+ "strconv"
)
func main() {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
- fmt.Println(len(dir))
+ got := len(dir)
+ want, _ := strconv.Atoi(os.Args[1])
+ if got != want {
+ fmt.Fprintf(os.Stderr, "got %d files; want %d\n", got, want)
+ os.Exit(1)
+ }
}
env GOCACHE=$WORK/gocache
# Test that minimization is working for recoverable errors.
-! go test -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=100x -fuzzminimizetime=10000x minimizer_test.go
+! go test -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=10000x minimizer_test.go
! stdout '^ok'
stdout 'got the minimum size!'
stdout 'contains a non-zero byte'
rm testdata
# Test that minimization is working for non-recoverable errors.
-! go test -fuzz=FuzzMinimizerNonrecoverable -run=FuzzMinimizerNonrecoverable -fuzztime=100x -fuzzminimizetime=10000x minimizer_test.go
+! go test -fuzz=FuzzMinimizerNonrecoverable -run=FuzzMinimizerNonrecoverable -fuzztime=10000x minimizer_test.go
! stdout '^ok'
stdout 'found a crash, minimizing'
stdout 'fuzzing process terminated unexpectedly while minimizing: exit status 99'
go test
# Running the fuzzer should find a crashing input quickly.
-! go test -fuzz=FuzzWithBug -fuzztime=100x
+! go test -fuzz=FuzzWithBug -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithBug[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzWithBug
stdout 'testdata[/\\]corpus[/\\]FuzzWithBug[/\\][a-f0-9]{64}'
stdout 'this input caused a crash!'
-! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=100x
+! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithNilPanic[/\\]'
stdout 'runtime.Goexit'
go run check_testdata.go FuzzWithNilPanic
-! go test -run=FuzzWithFail -fuzz=FuzzWithFail -fuzztime=100x
+! go test -run=FuzzWithFail -fuzz=FuzzWithFail -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithFail[/\\]'
go run check_testdata.go FuzzWithFail
-! go test -run=FuzzWithLogFail -fuzz=FuzzWithLogFail -fuzztime=100x
+! go test -run=FuzzWithLogFail -fuzz=FuzzWithLogFail -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithLogFail[/\\]'
stdout 'logged something'
go run check_testdata.go FuzzWithLogFail
-! go test -run=FuzzWithErrorf -fuzz=FuzzWithErrorf -fuzztime=100x
+! go test -run=FuzzWithErrorf -fuzz=FuzzWithErrorf -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithErrorf[/\\]'
stdout 'errorf was called here'
go run check_testdata.go FuzzWithErrorf
-! go test -run=FuzzWithFatalf -fuzz=FuzzWithFatalf -fuzztime=100x
+! go test -run=FuzzWithFatalf -fuzz=FuzzWithFatalf -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithFatalf[/\\]'
stdout 'fatalf was called here'
go run check_testdata.go FuzzWithFatalf
-! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=100x
+! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithBadExit[/\\]'
stdout 'unexpectedly'
go run check_testdata.go FuzzWithBadExit
# Running the fuzzer should find a crashing input quickly for fuzzing two types.
-! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=100x
+! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzWithTwoTypes[/\\]'
stdout 'these inputs caused a crash!'
go run check_testdata.go FuzzWithTwoTypes
# Running the fuzzer should find a crashing input quickly for an integer.
-! go test -run=FuzzInt -fuzz=FuzzInt -fuzztime=100x
+! go test -run=FuzzInt -fuzz=FuzzInt -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzInt[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzInt
-! go test -run=FuzzUint -fuzz=FuzzUint -fuzztime=100x
+! go test -run=FuzzUint -fuzz=FuzzUint -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzUint[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzUint
# Running the fuzzer should find a crashing input quickly for a bool.
-! go test -run=FuzzBool -fuzz=FuzzBool -fuzztime=100x
+! go test -run=FuzzBool -fuzz=FuzzBool -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzBool[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzBool
# Running the fuzzer should find a crashing input quickly for a float.
-! go test -run=FuzzFloat -fuzz=FuzzFloat -fuzztime=100x
+! go test -run=FuzzFloat -fuzz=FuzzFloat -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzFloat[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzFloat
# Running the fuzzer should find a crashing input quickly for a byte.
-! go test -run=FuzzByte -fuzz=FuzzByte -fuzztime=100x
+! go test -run=FuzzByte -fuzz=FuzzByte -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzByte[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzByte
# Running the fuzzer should find a crashing input quickly for a rune.
-! go test -run=FuzzRune -fuzz=FuzzRune -fuzztime=100x
+! go test -run=FuzzRune -fuzz=FuzzRune -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzRune[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzRune
# Running the fuzzer should find a crashing input quickly for a string.
-! go test -run=FuzzString -fuzz=FuzzString -fuzztime=100x
+! go test -run=FuzzString -fuzz=FuzzString -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]corpus[/\\]FuzzString[/\\]'
stdout 'this input caused a crash!'
go run check_testdata.go FuzzString
// MinimizeLimit is the maximum number of calls to the fuzz function to be
// made while minimizing after finding a crash. If zero, there will be
- // no limit.
+ // no limit. Calls to the fuzz function made when minimizing also count
+ // toward Limit.
MinimizeLimit int64
// parallel is the number of worker processes to run in parallel. If zero,
// Don't start more workers than we need.
opts.Parallel = int(opts.Limit)
}
- canMinimize := false
- for _, t := range opts.Types {
- if isMinimizable(t) {
- canMinimize = true
- break
- }
- }
c, err := newCoordinator(opts)
if err != nil {
}
if result.crasherMsg != "" {
- if canMinimize && !result.minimized {
- // Found a crasher but haven't yet attempted to minimize it.
- // Send it back to a worker for minimization. Disable inputC so
- // other workers don't continue fuzzing.
+ if c.canMinimize() && !result.minimizeAttempted {
if crashMinimizing {
+ // This crash is not minimized, and another crash is being minimized.
+ // Ignore this one and wait for the other one to finish.
break
}
+ // Found a crasher but haven't yet attempted to minimize it.
+ // Send it back to a worker for minimization. Disable inputC so
+ // other workers don't continue fuzzing.
crashMinimizing = true
inputC = nil
fmt.Fprintf(c.opts.Log, "found a crash, minimizing...\n")
- c.minimizeC <- result
+ c.minimizeC <- c.minimizeInputForResult(result)
} else if !crashWritten {
// Found a crasher that's either minimized or not minimizable.
// Write to corpus and stop.
// values from this starting point.
entry CorpusEntry
- // countRequested is the number of values to test. If non-zero, the worker
- // will stop after testing this many values, if it hasn't already stopped.
- countRequested int64
+ // timeout is the time to spend fuzzing variations of this input,
+ // not including starting or cleaning up.
+ timeout time.Duration
+
+ // limit is the maximum number of calls to the fuzz function the worker may
+ // make. The worker may make fewer calls, for example, if it finds an
+ // error early. If limit is zero, there is no limit on calls to the
+ // fuzz function.
+ limit int64
// coverageOnly indicates whether this input is for a coverage-only run. If
// true, the input should not be fuzzed.
// crasherMsg is an error message from a crash. It's "" if no crash was found.
crasherMsg string
- // minimized is true if a worker attempted to minimize entry.
- // Minimization may not have actually been completed.
- minimized bool
+ // minimizeAttempted is true if the worker attempted to minimize this input.
+ // The worker may or may not have succeeded.
+ minimizeAttempted bool
// coverageData is set if the worker found new coverage.
coverageData []byte
- // countRequested is the number of values the coordinator asked the worker
+ // limit is the number of values the coordinator asked the worker
// to test. 0 if there was no limit.
- countRequested int64
+ limit int64
// count is the number of values the worker actually tested.
count int64
entryDuration time.Duration
}
+type fuzzMinimizeInput struct {
+ // entry is an interesting value or crasher to minimize.
+ entry CorpusEntry
+
+ // crasherMsg is an error message from a crash. It's "" if no crash was found.
+ // If set, the worker will attempt to find a smaller input that also produces
+ // an error, though not necessarily the same error.
+ crasherMsg string
+
+ // limit is the maximum number of calls to the fuzz function the worker may
+ // make. The worker may make fewer calls, for example, if it can't reproduce
+ // an error. If limit is zero, there is no limit on calls to the fuzz function.
+ limit int64
+
+ // timeout is the time to spend minimizing this input.
+ // A zero timeout means no limit.
+ timeout time.Duration
+}
+
// coordinator holds channels that workers can use to communicate with
// the coordinator.
type coordinator struct {
// minimizeC is sent values to minimize by the coordinator. Any worker may
// receive values from this channel. Workers send results to resultC.
- minimizeC chan fuzzResult
+ minimizeC chan fuzzMinimizeInput
// resultC is sent results of fuzzing by workers. The coordinator
// receives these. Multiple types of messages are allowed.
// starting up or tearing down.
duration time.Duration
- // countWaiting is the number of values the coordinator is currently waiting
- // for workers to fuzz.
+ // countWaiting is the number of fuzzing executions the coordinator is
+ // waiting on workers to complete.
countWaiting int64
// corpus is a set of interesting values, including the seed corpus and
// which corpus value to send next (or generates something new).
corpusIndex int
+ // typesAreMinimizable is true if one or more of the types of fuzz function's
+ // parameters can be minimized.
+ typesAreMinimizable bool
+
// coverageMask aggregates coverage that was found for all inputs in the
// corpus. Each byte represents a single basic execution block. Each set bit
// within the byte indicates that an input has triggered that block at least
opts: opts,
startTime: time.Now(),
inputC: make(chan fuzzInput),
- minimizeC: make(chan fuzzResult),
+ minimizeC: make(chan fuzzMinimizeInput),
resultC: make(chan fuzzResult),
corpus: corpus,
covOnlyInputs: covOnlyInputs,
}
+ for _, t := range opts.Types {
+ if isMinimizable(t) {
+ c.typesAreMinimizable = true
+ break
+ }
+ }
covSize := len(coverage())
if covSize == 0 {
}
func (c *coordinator) updateStats(result fuzzResult) {
- // Adjust total stats.
c.count += result.count
- c.countWaiting -= result.countRequested
+ c.countWaiting -= result.limit
c.duration += result.totalDuration
}
entry: c.corpus.entries[c.corpusIndex],
interestingCount: c.interestingCount,
coverageData: make([]byte, len(c.coverageMask)),
+ timeout: workerFuzzDuration,
}
copy(input.coverageData, c.coverageMask)
c.corpusIndex = (c.corpusIndex + 1) % (len(c.corpus.entries))
}
if c.opts.Limit > 0 {
- input.countRequested = c.opts.Limit / int64(c.opts.Parallel)
+ input.limit = c.opts.Limit / int64(c.opts.Parallel)
if c.opts.Limit%int64(c.opts.Parallel) > 0 {
- input.countRequested++
+ input.limit++
}
remaining := c.opts.Limit - c.count - c.countWaiting
- if input.countRequested > remaining {
- input.countRequested = remaining
+ if input.limit > remaining {
+ input.limit = remaining
}
- c.countWaiting += input.countRequested
+ c.countWaiting += input.limit
}
return input, true
}
+// minimizeInputForResult returns an input for minimization based on the given
+// fuzzing result that either caused a failure or expanded coverage.
+func (c *coordinator) minimizeInputForResult(result fuzzResult) fuzzMinimizeInput {
+ input := fuzzMinimizeInput{
+ entry: result.entry,
+ crasherMsg: result.crasherMsg,
+ }
+ input.limit = 0
+ if c.opts.MinimizeTimeout > 0 {
+ input.timeout = c.opts.MinimizeTimeout
+ }
+ if c.opts.MinimizeLimit > 0 {
+ input.limit = c.opts.MinimizeLimit
+ } else if c.opts.Limit > 0 {
+ if result.crasherMsg != "" {
+ input.limit = c.opts.Limit
+ } else {
+ input.limit = c.opts.Limit / int64(c.opts.Parallel)
+ if c.opts.Limit%int64(c.opts.Parallel) > 0 {
+ input.limit++
+ }
+ }
+ }
+ remaining := c.opts.Limit - c.count - c.countWaiting
+ if input.limit > remaining {
+ input.limit = remaining
+ }
+ c.countWaiting += input.limit
+ return input
+}
+
func (c *coordinator) coverageOnlyRun() bool {
return c.covOnlyInputs > 0
}
return newBitCount
}
+// canMinimize returns whether the coordinator should attempt to find smaller
+// inputs that reproduce a crash or new coverage.
+func (c *coordinator) canMinimize() bool {
+ return c.typesAreMinimizable &&
+ (c.opts.Limit == 0 || c.count+c.countWaiting < c.opts.Limit)
+}
+
// readCache creates a combined corpus from seed values and values in the cache
// (in GOCACHE/fuzz).
//
case input := <-w.coordinator.inputC:
// Received input from coordinator.
- args := fuzzArgs{Limit: input.countRequested, Timeout: workerFuzzDuration, CoverageOnly: input.coverageOnly}
+ args := fuzzArgs{Limit: input.limit, Timeout: input.timeout, CoverageOnly: input.coverageOnly}
if interestingCount < input.interestingCount {
// The coordinator's coverage data has changed, so send the data
// to the client.
resp.Err = fmt.Sprintf("fuzzing process terminated unexpectedly: %v", w.waitErr)
}
result := fuzzResult{
- countRequested: input.countRequested,
- count: resp.Count,
- totalDuration: resp.TotalDuration,
- entryDuration: resp.InterestingDuration,
- entry: entry,
+ limit: input.limit,
+ count: resp.Count,
+ totalDuration: resp.TotalDuration,
+ entryDuration: resp.InterestingDuration,
+ entry: entry,
}
if resp.Err != "" {
result.crasherMsg = resp.Err
}
w.coordinator.resultC <- result
- case crasher := <-w.coordinator.minimizeC:
+ case input := <-w.coordinator.minimizeC:
// Received input to minimize from coordinator.
- minRes, err := w.minimize(ctx, crasher)
+ result, err := w.minimize(ctx, input)
if err != nil {
// Failed to minimize. Send back the original crash.
fmt.Fprintln(w.coordinator.opts.Log, err)
- minRes = crasher
- minRes.minimized = true
+ result = fuzzResult{
+ entry: input.entry,
+ crasherMsg: input.crasherMsg,
+ minimizeAttempted: true,
+ limit: input.limit,
+ }
}
- w.coordinator.resultC <- minRes
+ w.coordinator.resultC <- result
}
}
}
//
// TODO: support minimizing inputs that expand coverage in a specific way,
// for example, by ensuring that an input activates a specific set of counters.
-func (w *worker) minimize(ctx context.Context, input fuzzResult) (min fuzzResult, err error) {
+func (w *worker) minimize(ctx context.Context, input fuzzMinimizeInput) (min fuzzResult, err error) {
if w.coordinator.opts.MinimizeTimeout != 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, w.coordinator.opts.MinimizeTimeout)
defer cancel()
}
- min = input
- min.minimized = true
+ min = fuzzResult{
+ entry: input.entry,
+ crasherMsg: input.crasherMsg,
+ minimizeAttempted: true,
+ limit: input.limit,
+ }
args := minimizeArgs{
- Limit: w.coordinator.opts.MinimizeLimit,
- Timeout: w.coordinator.opts.MinimizeTimeout,
+ Limit: input.limit,
+ Timeout: input.timeout,
}
minEntry, resp, err := w.client.minimize(ctx, input.entry, args)
if err != nil {
if args.CoverageOnly {
fStart := time.Now()
- ws.fuzzFn(CorpusEntry{Values: vals})
+ err := ws.fuzzFn(CorpusEntry{Values: vals})
+ if err != nil {
+ resp.Err = err.Error()
+ if resp.Err == "" {
+ resp.Err = "fuzz function failed with no output"
+ }
+ return resp
+ }
resp.InterestingDuration = time.Since(fStart)
resp.CoverageData = coverageSnapshot
return resp