"fmt"
"io"
"io/ioutil"
+ "log"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
+ "sync"
"time"
)
err: errors.New(result.crasherMsg),
}
}
+ if printDebugInfo() {
+ log.Printf("DEBUG new crasher, id: %s, parent: %s, gen: %d, size: %d, exec time: %s\n", result.entry.Name, result.entry.Parent, result.entry.Generation, len(result.entry.Data), result.entryDuration)
+ }
stop(err)
}
} else if result.coverageData != nil {
- foundNew := c.updateCoverage(result.coverageData)
- if foundNew && !c.coverageOnlyRun() {
+ newEdges := c.updateCoverage(result.coverageData)
+ if newEdges > 0 && !c.coverageOnlyRun() {
// Found an interesting value that expanded coverage.
// This is not a crasher, but we should add it to the
// on-disk corpus, and prioritize it for future fuzzing.
stop(err)
}
}
+ if printDebugInfo() {
+ log.Printf("DEBUG new interesting input, id: %s, parent: %s, gen: %d, new edges: %d, total edges: %d, size: %d, exec time: %s\n", result.entry.Name, result.entry.Parent, result.entry.Generation, newEdges, countEdges(c.coverageData), len(result.entry.Data), result.entryDuration)
+ }
} else if c.coverageOnlyRun() {
c.covOnlyInputs--
+ if printDebugInfo() {
+ log.Printf("DEBUG processed an initial input, id: %s, new edges: %d, size: %d, exec time: %s\n", result.entry.Parent, newEdges, len(result.entry.Data), result.entryDuration)
+ }
if c.covOnlyInputs == 0 {
// The coordinator has finished getting a baseline for
// coverage. Tell all of the workers to inialize their
// baseline coverage data (by setting interestingCount
// to 0).
c.interestingCount = 0
+ if printDebugInfo() {
+ log.Printf("DEBUG finished processing input corpus, entries: %d, initial coverage edges: %d\n", len(c.corpus.entries), countEdges(c.coverageData))
+ }
+ }
+ } else {
+ if printDebugInfo() {
+ log.Printf("DEBUG worker reported interesting input that doesn't expand coverage, id: %s, parent: %s\n", result.entry.Name, result.entry.Parent)
}
}
}
// TODO: split marshalled and unmarshalled types. In most places, we only need
// one or the other.
type CorpusEntry = struct {
+ Parent string
+
// Name is the name of the corpus file, if the entry was loaded from the
// seed corpus. It can be used with -run. For entries added with f.Add and
// entries generated by the mutator, Name is empty.
// Values is the unmarshaled values from a corpus file.
Values []interface{}
+
+ Generation int
}
type fuzzInput struct {
// count is the number of values the worker actually tested.
count int64
- // duration is the time the worker spent testing inputs.
- duration time.Duration
+ // totalDuration is the time the worker spent testing inputs.
+ totalDuration time.Duration
+
+ // entryDuration is the time the worker spent execution an interesting result
+ entryDuration time.Duration
}
// coordinator holds channels that workers can use to communicate with
for _, t := range opts.Types {
vals = append(vals, zeroValue(t))
}
- corpus.entries = append(corpus.entries, CorpusEntry{Data: marshalCorpusFile(vals...), Values: vals})
+ data := marshalCorpusFile(vals...)
+ h := sha256.Sum256(data)
+ name := fmt.Sprintf("%x", h[:4])
+ corpus.entries = append(corpus.entries, CorpusEntry{Name: name, Data: data, Values: vals})
}
c := &coordinator{
opts: opts,
// Adjust total stats.
c.count += result.count
c.countWaiting -= result.countRequested
- c.duration += result.duration
+ c.duration += result.totalDuration
}
func (c *coordinator) logStats() {
// updateCoverage updates c.coverageData for all edges that have a higher
// counter value in newCoverage. It return true if a new edge was hit.
-func (c *coordinator) updateCoverage(newCoverage []byte) bool {
+func (c *coordinator) updateCoverage(newCoverage []byte) int {
if len(newCoverage) != len(c.coverageData) {
panic(fmt.Sprintf("num edges changed at runtime: %d, expected %d", len(newCoverage), len(c.coverageData)))
}
- newEdge := false
+ newEdges := 0
for i := range newCoverage {
if newCoverage[i] > c.coverageData[i] {
if c.coverageData[i] == 0 {
- newEdge = true
+ newEdges++
}
c.coverageData[i] = newCoverage[i]
}
}
- return newEdge
+ return newEdges
}
// readCache creates a combined corpus from seed values and values in the cache
uint32(0),
uint64(0),
}
+
+var (
+ debugInfo bool
+ debugInfoOnce sync.Once
+)
+
+func printDebugInfo() bool {
+ debugInfoOnce.Do(func() {
+ debug := strings.Split(os.Getenv("GODEBUG"), ",")
+ for _, f := range debug {
+ if f == "fuzzdebug=1" {
+ debugInfo = true
+ break
+ }
+ }
+ })
+ return debugInfo
+}
import (
"context"
+ "crypto/sha256"
"encoding/json"
"errors"
"fmt"
result := fuzzResult{
countRequested: input.countRequested,
count: resp.Count,
- duration: resp.Duration,
+ totalDuration: resp.TotalDuration,
+ entryDuration: resp.InterestingDuration,
}
if resp.Err != "" {
- result.entry = CorpusEntry{Data: value}
+ h := sha256.Sum256(value)
+ name := fmt.Sprintf("%x", h[:4])
+ result.entry = CorpusEntry{
+ Name: name,
+ Parent: input.entry.Name,
+ Data: value,
+ Generation: input.entry.Generation + 1,
+ }
result.crasherMsg = resp.Err
} else if resp.CoverageData != nil {
- result.entry = CorpusEntry{Data: value}
+ h := sha256.Sum256(value)
+ name := fmt.Sprintf("%x", h[:4])
+ result.entry = CorpusEntry{
+ Name: name,
+ Parent: input.entry.Name,
+ Data: value,
+ Generation: input.entry.Generation + 1,
+ }
result.coverageData = resp.CoverageData
}
w.coordinator.resultC <- result
}
min.crasherMsg = resp.Err
min.count = resp.Count
- min.duration = resp.Duration
+ min.totalDuration = resp.Duration
min.entry.Data = value
return min, nil
}
// fuzzResponse contains results from workerServer.fuzz.
type fuzzResponse struct {
// Duration is the time spent fuzzing, not including starting or cleaning up.
- Duration time.Duration
+ TotalDuration time.Duration
+ InterestingDuration time.Duration
// Count is the number of values tested.
Count int64
ws.coverageData = args.CoverageData
}
start := time.Now()
- defer func() { resp.Duration = time.Since(start) }()
+ defer func() { resp.TotalDuration = time.Since(start) }()
fuzzCtx, cancel := context.WithTimeout(ctx, args.Timeout)
defer cancel()
}
if args.CoverageOnly {
+ fStart := time.Now()
ws.fuzzFn(CorpusEntry{Values: vals})
+ resp.InterestingDuration = time.Since(fStart)
resp.CoverageData = coverageSnapshot
return resp
}
mem.header().count++
ws.m.mutate(vals, cap(mem.valueRef()))
writeToMem(vals, mem)
- if err := ws.fuzzFn(CorpusEntry{Values: vals}); err != nil {
+ fStart := time.Now()
+ err := ws.fuzzFn(CorpusEntry{Values: vals})
+ fDur := time.Since(fStart)
+ if err != nil {
resp.Err = err.Error()
if resp.Err == "" {
resp.Err = "fuzz function failed with no output"
if ws.coverageData[i] == 0 && coverageSnapshot[i] > ws.coverageData[i] {
// TODO(jayconrod,katie): minimize this.
resp.CoverageData = coverageSnapshot
+ resp.InterestingDuration = fDur
return resp
}
}