aboutsummaryrefslogtreecommitdiff
path: root/src/internal
diff options
context:
space:
mode:
authorKatie Hockman <katie@golang.org>2021-11-03 14:44:16 -0400
committerKatie Hockman <katie@golang.org>2021-11-04 20:01:10 +0000
commit9b2dd1f7714b38f1bfe25676357b62c1bb4cad64 (patch)
tree6237584bcb313b149da11c6a0528f9de7d9b7bf5 /src/internal
parent961aab26bffece299f1528b44d260ea0a921ac56 (diff)
downloadgo-9b2dd1f7714b38f1bfe25676357b62c1bb4cad64.tar.gz
go-9b2dd1f7714b38f1bfe25676357b62c1bb4cad64.zip
internal/fuzz: fix internal error handling
This doesn't handle every possible scenario, but improves the one we can control. For example, if the worker panics for some reason, we have no way of knowing whether the panic occurred in an expected way (while executing the fuzz target) or due to an internal error in the worker. So any panic will still be treated as a crash. However, if it fails due to some internal bug that we know how to catch, then the error should be reported to the user without a new crasher being written to testdata. This is very difficult to test. The reasons an internal error would occur is because something went very wrong, and we have a bug in our code (which is why they were previously panics). So simulating a problem like this in a test is not really feasible. Fixes #48804 Change-Id: I334618f84eb4a994a8d17419551a510b1fdef071 Reviewed-on: https://go-review.googlesource.com/c/go/+/361115 Trust: Katie Hockman <katie@golang.org> Run-TryBot: Katie Hockman <katie@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org>
Diffstat (limited to 'src/internal')
-rw-r--r--src/internal/fuzz/worker.go47
-rw-r--r--src/internal/fuzz/worker_test.go2
2 files changed, 28 insertions, 21 deletions
diff --git a/src/internal/fuzz/worker.go b/src/internal/fuzz/worker.go
index 388675f713..02efa7f84a 100644
--- a/src/internal/fuzz/worker.go
+++ b/src/internal/fuzz/worker.go
@@ -153,7 +153,7 @@ func (w *worker) coordinate(ctx context.Context) error {
Warmup: input.warmup,
CoverageData: input.coverageData,
}
- entry, resp, err := w.client.fuzz(ctx, input.entry, args)
+ entry, resp, isInternalError, err := w.client.fuzz(ctx, input.entry, args)
canMinimize := true
if err != nil {
// Error communicating with worker.
@@ -167,14 +167,6 @@ func (w *worker) coordinate(ctx context.Context) error {
// Report an error, but don't record a crasher.
return fmt.Errorf("communicating with fuzzing process: %v", err)
}
- if w.waitErr == nil || isInterruptError(w.waitErr) {
- // Worker stopped, either by exiting with status 0 or after being
- // interrupted with a signal (not sent by coordinator). See comment in
- // termC case above.
- //
- // Since we expect I/O errors around interrupts, ignore this error.
- return nil
- }
if sig, ok := terminationSignal(w.waitErr); ok && !isCrashSignal(sig) {
// Worker terminated by a signal that probably wasn't caused by a
// specific input to the fuzz function. For example, on Linux,
@@ -183,6 +175,11 @@ func (w *worker) coordinate(ctx context.Context) error {
// is closed. Don't record a crasher.
return fmt.Errorf("fuzzing process terminated by unexpected signal; no crash will be recorded: %v", w.waitErr)
}
+ if isInternalError {
+ // An internal error occurred which shouldn't be considered
+ // a crash.
+ return err
+ }
// Unexpected termination. Set error message and fall through.
// We'll restart the worker on the next iteration.
// Don't attempt to minimize this since it crashed the worker.
@@ -567,6 +564,10 @@ type fuzzResponse struct {
// Err is the error string caused by the value in shared memory, which is
// non-empty if the value in shared memory caused a crash.
Err string
+
+ // InternalErr is the error string caused by an internal error in the
+ // worker. This shouldn't be considered a crasher.
+ InternalErr string
}
// pingArgs contains arguments to workerServer.ping.
@@ -663,7 +664,8 @@ func (ws *workerServer) serve(ctx context.Context) error {
func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) (resp fuzzResponse) {
if args.CoverageData != nil {
if ws.coverageMask != nil && len(args.CoverageData) != len(ws.coverageMask) {
- panic(fmt.Sprintf("unexpected size for CoverageData: got %d, expected %d", len(args.CoverageData), len(ws.coverageMask)))
+ resp.InternalErr = fmt.Sprintf("unexpected size for CoverageData: got %d, expected %d", len(args.CoverageData), len(ws.coverageMask))
+ return resp
}
ws.coverageMask = args.CoverageData
}
@@ -682,12 +684,14 @@ func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) (resp fuzzRespo
ws.memMu <- mem
}()
if args.Limit > 0 && mem.header().count >= args.Limit {
- panic(fmt.Sprintf("mem.header().count %d already exceeds args.Limit %d", mem.header().count, args.Limit))
+ resp.InternalErr = fmt.Sprintf("mem.header().count %d already exceeds args.Limit %d", mem.header().count, args.Limit)
+ return resp
}
vals, err := unmarshalCorpusFile(mem.valueCopy())
if err != nil {
- panic(err)
+ resp.InternalErr = err.Error()
+ return resp
}
shouldStop := func() bool {
@@ -1027,7 +1031,7 @@ func (wc *workerClient) minimize(ctx context.Context, entryIn CorpusEntry, args
entryOut.Parent = entryIn.Parent
entryOut.Generation = entryIn.Generation
if err != nil {
- panic(fmt.Sprintf("workerClient.minimize unmarshaling minimized value: %v", err))
+ return CorpusEntry{}, minimizeResponse{}, fmt.Errorf("workerClient.minimize unmarshaling minimized value: %v", err)
}
} else {
// Did not minimize, but the original input may still be interesting,
@@ -1039,40 +1043,43 @@ func (wc *workerClient) minimize(ctx context.Context, entryIn CorpusEntry, args
}
// fuzz tells the worker to call the fuzz method. See workerServer.fuzz.
-func (wc *workerClient) fuzz(ctx context.Context, entryIn CorpusEntry, args fuzzArgs) (entryOut CorpusEntry, resp fuzzResponse, err error) {
+func (wc *workerClient) fuzz(ctx context.Context, entryIn CorpusEntry, args fuzzArgs) (entryOut CorpusEntry, resp fuzzResponse, isInternalError bool, err error) {
wc.mu.Lock()
defer wc.mu.Unlock()
mem, ok := <-wc.memMu
if !ok {
- return CorpusEntry{}, fuzzResponse{}, errSharedMemClosed
+ return CorpusEntry{}, fuzzResponse{}, true, errSharedMemClosed
}
mem.header().count = 0
inp, err := CorpusEntryData(entryIn)
if err != nil {
- return CorpusEntry{}, fuzzResponse{}, err
+ return CorpusEntry{}, fuzzResponse{}, true, err
}
mem.setValue(inp)
wc.memMu <- mem
c := call{Fuzz: &args}
callErr := wc.callLocked(ctx, c, &resp)
+ if resp.InternalErr != "" {
+ return CorpusEntry{}, fuzzResponse{}, true, errors.New(resp.InternalErr)
+ }
mem, ok = <-wc.memMu
if !ok {
- return CorpusEntry{}, fuzzResponse{}, errSharedMemClosed
+ return CorpusEntry{}, fuzzResponse{}, true, errSharedMemClosed
}
defer func() { wc.memMu <- mem }()
resp.Count = mem.header().count
if !bytes.Equal(inp, mem.valueRef()) {
- panic("workerServer.fuzz modified input")
+ return CorpusEntry{}, fuzzResponse{}, true, errors.New("workerServer.fuzz modified input")
}
needEntryOut := callErr != nil || resp.Err != "" ||
(!args.Warmup && resp.CoverageData != nil)
if needEntryOut {
valuesOut, err := unmarshalCorpusFile(inp)
if err != nil {
- panic(fmt.Sprintf("unmarshaling fuzz input value after call: %v", err))
+ return CorpusEntry{}, fuzzResponse{}, true, fmt.Errorf("unmarshaling fuzz input value after call: %v", err)
}
wc.m.r.restore(mem.header().randState, mem.header().randInc)
if !args.Warmup {
@@ -1098,7 +1105,7 @@ func (wc *workerClient) fuzz(ctx context.Context, entryIn CorpusEntry, args fuzz
}
}
- return entryOut, resp, callErr
+ return entryOut, resp, false, callErr
}
// ping tells the worker to call the ping method. See workerServer.ping.
diff --git a/src/internal/fuzz/worker_test.go b/src/internal/fuzz/worker_test.go
index e32770b02b..c6f83fd08d 100644
--- a/src/internal/fuzz/worker_test.go
+++ b/src/internal/fuzz/worker_test.go
@@ -96,7 +96,7 @@ func BenchmarkWorkerFuzz(b *testing.B) {
Limit: int64(b.N) - i,
Timeout: workerFuzzDuration,
}
- _, resp, err := w.client.fuzz(context.Background(), entry, args)
+ _, resp, _, err := w.client.fuzz(context.Background(), entry, args)
if err != nil {
b.Fatal(err)
}