mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 14:21:14 +00:00
test/fuzz: convert to Go 1.18 native fuzzing (#8359)
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
.PHONY: fuzz-mempool
|
||||
fuzz-mempool:
|
||||
cd mempool && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
.PHONY: fuzz-p2p-sc
|
||||
fuzz-p2p-sc:
|
||||
cd p2p/secretconnection && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go run ./init-corpus/main.go && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
.PHONY: fuzz-rpc-server
|
||||
fuzz-rpc-server:
|
||||
cd rpc/jsonrpc/server && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
clean:
|
||||
find . -name corpus -type d -exec rm -rf {} +;
|
||||
find . -name crashers -type d -exec rm -rf {} +;
|
||||
find . -name suppressions -type d -exec rm -rf {} +;
|
||||
find . -name *\.zip -type f -delete
|
||||
@@ -5,68 +5,18 @@ Fuzzing for various packages in Tendermint using [go-fuzz](https://github.com/dv
|
||||
Inputs:
|
||||
|
||||
- mempool `CheckTx` (using kvstore in-process ABCI app)
|
||||
- p2p `Addrbook#AddAddress`
|
||||
- p2p `pex.Reactor#Receive`
|
||||
- p2p `SecretConnection#Read` and `SecretConnection#Write`
|
||||
- rpc jsonrpc server
|
||||
|
||||
## Directory structure
|
||||
|
||||
```
|
||||
| test
|
||||
| |- corpus/
|
||||
| |- crashers/
|
||||
| |- init-corpus/
|
||||
| |- suppressions/
|
||||
| |- testdata/
|
||||
| |- <testname>.go
|
||||
```
|
||||
|
||||
`/corpus` directory contains corpus data. The idea is to help the fuzzier to
|
||||
understand what bytes sequences are semantically valid (e.g. if we're testing
|
||||
PNG decoder, then we would put black-white PNG into corpus directory; with
|
||||
blockchain reactor - we would put blockchain messages into corpus).
|
||||
|
||||
`/init-corpus` (if present) contains a script for generating corpus data.
|
||||
|
||||
`/testdata` directory may contain an additional data (like `addrbook.json`).
|
||||
|
||||
Upon running the fuzzier, `/crashers` and `/suppressions` dirs will be created,
|
||||
along with <testname>.zip archive. `/crashers` will show any inputs, which have
|
||||
lead to panics (plus a trace). `/suppressions` will show any suppressed inputs.
|
||||
|
||||
## Running
|
||||
|
||||
```sh
|
||||
make fuzz-mempool
|
||||
make fuzz-p2p-addrbook
|
||||
make fuzz-p2p-pex
|
||||
make fuzz-p2p-sc
|
||||
make fuzz-rpc-server
|
||||
```
|
||||
|
||||
Each command will create corpus data (if needed), generate a fuzz archive and
|
||||
call `go-fuzz` executable.
|
||||
|
||||
Then watch out for the respective outputs in the fuzzer output to announce new
|
||||
crashers which can be found in the directory `crashers`.
|
||||
|
||||
For example if we find
|
||||
The fuzz tests are in native Go fuzzing format. Use the `go`
|
||||
tool to run them:
|
||||
|
||||
```sh
|
||||
ls crashers/
|
||||
61bde465f47c93254d64d643c3b2480e0a54666e
|
||||
61bde465f47c93254d64d643c3b2480e0a54666e.output
|
||||
61bde465f47c93254d64d643c3b2480e0a54666e.quoted
|
||||
da39a3ee5e6b4b0d3255bfef95601890afd80709
|
||||
da39a3ee5e6b4b0d3255bfef95601890afd80709.output
|
||||
da39a3ee5e6b4b0d3255bfef95601890afd80709.quoted
|
||||
go test -fuzz Mempool ./tests
|
||||
go test -fuzz P2PSecretConnection ./tests
|
||||
go test -fuzz RPCJSONRPCServer ./tests
|
||||
```
|
||||
|
||||
the crashing bytes generated by the fuzzer will be in
|
||||
`61bde465f47c93254d64d643c3b2480e0a54666e` the respective crash report in
|
||||
`61bde465f47c93254d64d643c3b2480e0a54666e.output`
|
||||
|
||||
and the bug report can be created by retrieving the bytes in
|
||||
`61bde465f47c93254d64d643c3b2480e0a54666e` and feeding those back into the
|
||||
`Fuzz` function.
|
||||
See [the Go Fuzzing introduction](https://go.dev/doc/fuzz/) for more information.
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package mempool_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
mempool "github.com/tendermint/tendermint/test/fuzz/mempool"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
|
||||
func TestMempoolTestdataCases(t *testing.T) {
|
||||
entries, err := os.ReadDir(testdataCasesDir)
|
||||
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/cases test panic")
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
mempool.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
0
test/fuzz/mempool/testdata/cases/empty
vendored
0
test/fuzz/mempool/testdata/cases/empty
vendored
@@ -1,10 +1,22 @@
|
||||
#!/bin/bash -eu
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export FUZZ_ROOT="github.com/tendermint/tendermint"
|
||||
|
||||
(cd test/fuzz/p2p/secretconnection; go run ./init-corpus/main.go)
|
||||
compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/p2p/secretconnection Fuzz fuzz_p2p_secretconnection fuzz
|
||||
build_go_fuzzer() {
|
||||
local function="$1"
|
||||
local fuzzer="$2"
|
||||
|
||||
compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/mempool Fuzz fuzz_mempool fuzz
|
||||
gotip run github.com/orijtech/otils/corpus2ossfuzz@latest -o "$OUT"/"$fuzzer"_seed_corpus.zip -corpus test/fuzz/tests/testdata/fuzz/"$function"
|
||||
compile_native_go_fuzzer "$FUZZ_ROOT"/test/fuzz/tests "$function" "$fuzzer"
|
||||
}
|
||||
|
||||
compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/rpc/jsonrpc/server Fuzz fuzz_rpc_jsonrpc_server fuzz
|
||||
gotip get github.com/AdamKorcz/go-118-fuzz-build/utils
|
||||
gotip get github.com/prometheus/common/expfmt@v0.32.1
|
||||
|
||||
build_go_fuzzer FuzzP2PSecretConnection fuzz_p2p_secretconnection
|
||||
|
||||
build_go_fuzzer FuzzMempool fuzz_mempool
|
||||
|
||||
build_go_fuzzer FuzzRPCJSONRPCServer fuzz_rpc_jsonrpc_server
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package secretconnection_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/test/fuzz/p2p/secretconnection"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
|
||||
func TestSecretConnectionTestdataCases(t *testing.T) {
|
||||
entries, err := os.ReadDir(testdataCasesDir)
|
||||
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/cases test panic")
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
secretconnection.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// nolint: gosec
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
baseDir := flag.String("base", ".", `where the "corpus" directory will live`)
|
||||
flag.Parse()
|
||||
|
||||
initCorpus(*baseDir)
|
||||
}
|
||||
|
||||
func initCorpus(baseDir string) {
|
||||
log.SetFlags(0)
|
||||
|
||||
corpusDir := filepath.Join(baseDir, "corpus")
|
||||
if err := os.MkdirAll(corpusDir, 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
data := []string{
|
||||
"dadc04c2-cfb1-4aa9-a92a-c0bf780ec8b6",
|
||||
"",
|
||||
" ",
|
||||
" a ",
|
||||
`{"a": 12, "tsp": 999, k: "blue"}`,
|
||||
`9999.999`,
|
||||
`""`,
|
||||
`Tendermint fuzzing`,
|
||||
}
|
||||
|
||||
for i, datum := range data {
|
||||
filename := filepath.Join(corpusDir, fmt.Sprintf("%d", i))
|
||||
|
||||
if err := os.WriteFile(filename, []byte(datum), 0644); err != nil {
|
||||
log.Fatalf("can't write %v to %q: %v", datum, filename, err)
|
||||
}
|
||||
|
||||
log.Printf("wrote %q", filename)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/test/fuzz/rpc/jsonrpc/server"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
|
||||
func TestServerTestdataCases(t *testing.T) {
|
||||
entries, err := os.ReadDir(testdataCasesDir)
|
||||
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/cases test panic")
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
server.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
rs "github.com/tendermint/tendermint/rpc/jsonrpc/server"
|
||||
"github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
||||
)
|
||||
|
||||
var rpcFuncMap = map[string]*rs.RPCFunc{
|
||||
"c": rs.NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) {
|
||||
return "foo", nil
|
||||
}, "s", "i"),
|
||||
}
|
||||
var mux *http.ServeMux
|
||||
|
||||
func init() {
|
||||
mux = http.NewServeMux()
|
||||
rs.RegisterRPCFuncs(mux, rpcFuncMap, log.NewNopLogger())
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
if len(data) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("POST", "http://localhost/", bytes.NewReader(data))
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
res := rec.Result()
|
||||
blob, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := res.Body.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(blob) == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if outputJSONIsSlice(blob) {
|
||||
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 outputJSONIsSlice(input []byte) bool {
|
||||
slice := []interface{}{}
|
||||
return json.Unmarshal(input, &slice) == nil
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[0]
|
||||
@@ -1 +0,0 @@
|
||||
[0]
|
||||
@@ -1 +0,0 @@
|
||||
[0,0]
|
||||
@@ -1 +0,0 @@
|
||||
[{"iD":7},{"iD":7}]
|
||||
@@ -1 +0,0 @@
|
||||
[{"iD":7},{"iD":7}]
|
||||
@@ -1,7 +1,10 @@
|
||||
package mempool
|
||||
//go:build gofuzz || go1.18
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
@@ -33,11 +36,11 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
err := getMp().CheckTx(context.Background(), data, nil, mempool.TxInfo{})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
func FuzzMempool(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
err := getMp().CheckTx(context.Background(), data, nil, mempool.TxInfo{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,19 +1,28 @@
|
||||
package secretconnection
|
||||
//go:build gofuzz || go1.18
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/internal/libs/async"
|
||||
sc "github.com/tendermint/tendermint/internal/p2p/conn"
|
||||
)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
func FuzzP2PSecretConnection(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
fuzz(data)
|
||||
})
|
||||
}
|
||||
|
||||
func fuzz(data []byte) {
|
||||
if len(data) == 0 {
|
||||
return -1
|
||||
return
|
||||
}
|
||||
|
||||
fooConn, barConn := makeSecretConnPair()
|
||||
@@ -50,8 +59,6 @@ func Fuzz(data []byte) int {
|
||||
if !bytes.Equal(data, dataRead) {
|
||||
panic("bytes written != read")
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
type kvstoreConn struct {
|
||||
68
test/fuzz/tests/rpc_jsonrpc_server_test.go
Normal file
68
test/fuzz/tests/rpc_jsonrpc_server_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
//go:build gofuzz || go1.18
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
|
||||
"github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
||||
)
|
||||
|
||||
func FuzzRPCJSONRPCServer(f *testing.F) {
|
||||
var rpcFuncMap = map[string]*rpcserver.RPCFunc{
|
||||
"c": rpcserver.NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) {
|
||||
return "foo", nil
|
||||
}, "s", "i"),
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
rpcserver.RegisterRPCFuncs(mux, rpcFuncMap, log.NewNopLogger())
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "http://localhost/", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
res := rec.Result()
|
||||
blob, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := res.Body.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(blob) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if outputJSONIsSlice(blob) {
|
||||
var recv []types.RPCResponse
|
||||
if err := json.Unmarshal(blob, &recv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var recv types.RPCResponse
|
||||
if err := json.Unmarshal(blob, &recv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func outputJSONIsSlice(input []byte) bool {
|
||||
var slice []json.RawMessage
|
||||
return json.Unmarshal(input, &slice) == nil
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("S1")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte(" ")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("{\"a\": 12, \"tsp\": 999, k: \"blue\"}")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\"\"")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("9999.999")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte(" a ")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("Tendermint fuzzing")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("[{\"iD\":7},{\"iD\":7}]")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("[0,0]")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("[0]")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("")
|
||||
Reference in New Issue
Block a user