From c5dc3b267f68e167c0e58bdcf50a29ff4df48349 Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Mon, 26 Jul 2021 10:58:51 -0400 Subject: [PATCH] test/fuzz: add test to reproduce found fuzz errors (#6757) This change does two things: 1. It fixes the json fuzzer to account for receiving array results. Arrays are returned by the rpc server when the input data is an array. 2. Adds a `fuzz_test.go` file and corresponding `testdata` directory containing the failing test case. This seems like a reasonable way to add and track previous crash issues in our fuzz test cases. The upcoming stdlib go fuzz tool does effectively this automatically. --- test/fuzz/rpc/jsonrpc/server/fuzz_test.go | 33 +++++++++++++++++++ test/fuzz/rpc/jsonrpc/server/handler.go | 23 ++++++++++--- .../1184f5b8d4b6dd08709cf1513f26744167065e0d | 1 + ...d-fuzz_rpc_jsonrpc_server-4738572803506176 | 1 + 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 test/fuzz/rpc/jsonrpc/server/fuzz_test.go create mode 100644 test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d create mode 100644 test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 diff --git a/test/fuzz/rpc/jsonrpc/server/fuzz_test.go b/test/fuzz/rpc/jsonrpc/server/fuzz_test.go new file mode 100644 index 000000000..005248375 --- /dev/null +++ b/test/fuzz/rpc/jsonrpc/server/fuzz_test.go @@ -0,0 +1,33 @@ +package server_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/test/fuzz/rpc/jsonrpc/server" +) + +const testdataDir = "testdata" + +func TestServerOnTestData(t *testing.T) { + entries, err := os.ReadDir(testdataDir) + require.NoError(t, err) + + for _, e := range entries { + entry := e + t.Run(entry.Name(), func(t *testing.T) { + defer func() { + r := recover() + require.Nilf(t, r, "testdata test panic") + }() + f, err := os.Open(filepath.Join(testdataDir, entry.Name())) + require.NoError(t, err) + input, err := ioutil.ReadAll(f) + require.NoError(t, err) + server.Fuzz(input) + }) + } +} diff --git a/test/fuzz/rpc/jsonrpc/server/handler.go b/test/fuzz/rpc/jsonrpc/server/handler.go index cab1b6ed0..c30a8890c 100644 --- a/test/fuzz/rpc/jsonrpc/server/handler.go +++ b/test/fuzz/rpc/jsonrpc/server/handler.go @@ -1,4 +1,4 @@ -package handler +package server import ( "bytes" @@ -39,11 +39,26 @@ func Fuzz(data []byte) int { if err := res.Body.Close(); err != nil { panic(err) } - if len(blob) > 0 { - recv := new(types.RPCResponse) - if err := json.Unmarshal(blob, recv); err != nil { + if len(blob) == 0 { + return 1 + } + + if inputJSONIsMultiElementSlice(data) { + recv := []types.RPCResponse{} + if err := json.Unmarshal(blob, &recv); err != nil { panic(err) } + return 1 + } + recv := &types.RPCResponse{} + if err := json.Unmarshal(blob, recv); err != nil { + panic(err) } return 1 } + +func inputJSONIsMultiElementSlice(input []byte) bool { + slice := []interface{}{} + err := json.Unmarshal(input, &slice) + return err == nil && len(slice) > 1 +} diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d b/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d new file mode 100644 index 000000000..6e7ea636e --- /dev/null +++ b/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d @@ -0,0 +1 @@ +[0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 b/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 new file mode 100644 index 000000000..0f7836d2f --- /dev/null +++ b/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 @@ -0,0 +1 @@ +[{"iD":7},{"iD":7}] \ No newline at end of file