test/fuzz: bring changes from v0.34.x into main (#9299)

This commit is contained in:
Elias Naur
2022-09-11 16:27:42 +02:00
committed by GitHub
parent 0f45086c5f
commit ac866e38d4
27 changed files with 203 additions and 2170 deletions
-39
View File
@@ -1,39 +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-addrbook
fuzz-p2p-addrbook:
cd p2p/addrbook && \
rm -f *-fuzz.zip && \
go run ./init-corpus/main.go && \
go-fuzz-build && \
go-fuzz
.PHONY: fuzz-p2p-pex
fuzz-p2p-pex:
cd p2p/pex && \
rm -f *-fuzz.zip && \
go run ./init-corpus/main.go && \
go-fuzz-build && \
go-fuzz
.PHONY: fuzz-p2p-sc
fuzz-p2p-sc:
cd p2p/secret_connection && \
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
+8 -57
View File
@@ -1,72 +1,23 @@
# fuzz
Fuzzing for various packages in Tendermint using [go-fuzz](https://github.com/dvyukov/go-fuzz) library.
Fuzzing for various packages in Tendermint using the fuzzing infrastructure included in
Go 1.18.
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.
+19 -6
View File
@@ -1,10 +1,23 @@
#!/bin/bash
# This script is invoked by OSS-Fuzz to run fuzz tests against Tendermint core.
# See https://github.com/google/oss-fuzz/blob/master/projects/tendermint/build.sh
set -euo pipefail
compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/mempool/v0 Fuzz mempool_v0_fuzzer
compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/mempool/v1 Fuzz mempool_v1_fuzzer
compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/p2p/addrbook Fuzz p2p_addrbook_fuzzer
compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/p2p/pex Fuzz p2p_pex_fuzzer
compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/p2p/secret_connection Fuzz p2p_secret_connection_fuzzer
compile_go_fuzzer github.com/tendermint/tendermint/test/fuzz/rpc/jsonrpc/server Fuzz rpc_jsonrpc_server_fuzzer
export FUZZ_ROOT="github.com/tendermint/tendermint"
build_go_fuzzer() {
local function="$1"
local fuzzer="$2"
go 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"
}
go get github.com/AdamKorcz/go-118-fuzz-build/utils
go 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
-34
View File
@@ -1,34 +0,0 @@
package addr
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/p2p/pex"
)
var addrBook = pex.NewAddrBook("./testdata/addrbook.json", true)
func Fuzz(data []byte) int {
addr := new(p2p.NetAddress)
if err := json.Unmarshal(data, addr); err != nil {
return -1
}
// Fuzz AddAddress.
err := addrBook.AddAddress(addr, addr)
if err != nil {
return 0
}
// Also, make sure PickAddress always returns a non-nil address.
bias := rand.Intn(100) //nolint:gosec
if p := addrBook.PickAddress(bias); p == nil {
panic(fmt.Sprintf("picked a nil address (bias: %d, addrBook size: %v)",
bias, addrBook.Size()))
}
return 1
}
@@ -1,57 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"os"
"path/filepath"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/p2p"
)
func main() {
baseDir := flag.String("base", ".", `where the "corpus" directory will live`)
flag.Parse()
initCorpus(*baseDir)
}
func initCorpus(baseDir string) {
log.SetFlags(0)
// create "corpus" directory
corpusDir := filepath.Join(baseDir, "corpus")
if err := os.MkdirAll(corpusDir, 0o755); err != nil {
log.Fatalf("Creating %q err: %v", corpusDir, err)
}
// create corpus
privKey := ed25519.GenPrivKey()
addrs := []*p2p.NetAddress{
{ID: p2p.PubKeyToID(privKey.PubKey()), IP: net.IPv4(0, 0, 0, 0), Port: 0},
{ID: p2p.PubKeyToID(privKey.PubKey()), IP: net.IPv4(127, 0, 0, 0), Port: 80},
{ID: p2p.PubKeyToID(privKey.PubKey()), IP: net.IPv4(213, 87, 10, 200), Port: 8808},
{ID: p2p.PubKeyToID(privKey.PubKey()), IP: net.IPv4(111, 111, 111, 111), Port: 26656},
{ID: p2p.PubKeyToID(privKey.PubKey()), IP: net.ParseIP("2001:db8::68"), Port: 26656},
}
for i, addr := range addrs {
filename := filepath.Join(corpusDir, fmt.Sprintf("%d.json", i))
bz, err := json.Marshal(addr)
if err != nil {
log.Fatalf("can't marshal %v: %v", addr, err)
}
//nolint:gosec
if err := os.WriteFile(filename, bz, 0o644); err != nil {
log.Fatalf("can't write %v to %q: %v", addr, filename, err)
}
log.Printf("wrote %q", filename)
}
}
-81
View File
@@ -1,81 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"os"
"path/filepath"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/p2p"
tmp2p "github.com/tendermint/tendermint/proto/tendermint/p2p"
)
func main() {
baseDir := flag.String("base", ".", `where the "corpus" directory will live`)
flag.Parse()
initCorpus(*baseDir)
}
//nolint:gosec
func initCorpus(rootDir string) {
log.SetFlags(0)
corpusDir := filepath.Join(rootDir, "corpus")
if err := os.MkdirAll(corpusDir, 0o755); err != nil {
log.Fatalf("Creating %q err: %v", corpusDir, err)
}
sizes := []int{0, 1, 2, 17, 5, 31}
// Make the PRNG predictable
rand.Seed(10)
for _, n := range sizes {
var addrs []*p2p.NetAddress
// IPv4 addresses
for i := 0; i < n; i++ {
privKey := ed25519.GenPrivKey()
addr := fmt.Sprintf(
"%s@%v.%v.%v.%v:26656",
p2p.PubKeyToID(privKey.PubKey()),
rand.Int()%256,
rand.Int()%256,
rand.Int()%256,
rand.Int()%256,
)
netAddr, _ := p2p.NewNetAddressString(addr)
addrs = append(addrs, netAddr)
}
// IPv6 addresses
privKey := ed25519.GenPrivKey()
ipv6a, err := p2p.NewNetAddressString(
fmt.Sprintf("%s@[ff02::1:114]:26656", p2p.PubKeyToID(privKey.PubKey())))
if err != nil {
log.Fatalf("can't create a new netaddress: %v", err)
}
addrs = append(addrs, ipv6a)
msg := tmp2p.Message{
Sum: &tmp2p.Message_PexAddrs{
PexAddrs: &tmp2p.PexAddrs{Addrs: p2p.NetAddressesToProto(addrs)},
},
}
bz, err := msg.Marshal()
if err != nil {
log.Fatalf("unable to marshal: %v", err)
}
filename := filepath.Join(rootDir, "corpus", fmt.Sprintf("%d", n))
if err := os.WriteFile(filename, bz, 0o644); err != nil {
log.Fatalf("can't write %X to %q: %v", bz, filename, err)
}
log.Printf("wrote %q", filename)
}
}
-86
View File
@@ -1,86 +0,0 @@
package pex
import (
"net"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/p2p/pex"
"github.com/tendermint/tendermint/version"
)
var (
pexR *pex.Reactor
peer p2p.Peer
)
func init() {
addrB := pex.NewAddrBook("./testdata/addrbook1", false)
pexR := pex.NewReactor(addrB, &pex.ReactorConfig{SeedMode: false})
if pexR == nil {
panic("NewReactor returned nil")
}
pexR.SetLogger(log.NewNopLogger())
peer := newFuzzPeer()
pexR.AddPeer(peer)
}
func Fuzz(data []byte) int {
// MakeSwitch uses log.TestingLogger which can't be executed in init()
cfg := config.DefaultP2PConfig()
cfg.PexReactor = true
sw := p2p.MakeSwitch(cfg, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
return sw
})
pexR.SetSwitch(sw)
pexR.Receive(pex.PexChannel, peer, data)
return 1
}
type fuzzPeer struct {
*service.BaseService
m map[string]interface{}
}
var _ p2p.Peer = (*fuzzPeer)(nil)
func newFuzzPeer() *fuzzPeer {
fp := &fuzzPeer{m: make(map[string]interface{})}
fp.BaseService = service.NewBaseService(nil, "fuzzPeer", fp)
return fp
}
var privKey = ed25519.GenPrivKey()
var nodeID = p2p.PubKeyToID(privKey.PubKey())
var defaultNodeInfo = p2p.DefaultNodeInfo{
ProtocolVersion: p2p.NewProtocolVersion(
version.P2PProtocol,
version.BlockProtocol,
0,
),
DefaultNodeID: nodeID,
ListenAddr: "0.0.0.0:98992",
Moniker: "foo1",
}
func (fp *fuzzPeer) FlushStop() {}
func (fp *fuzzPeer) ID() p2p.ID { return nodeID }
func (fp *fuzzPeer) RemoteIP() net.IP { return net.IPv4(0, 0, 0, 0) }
func (fp *fuzzPeer) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: fp.RemoteIP(), Port: 98991, Zone: ""}
}
func (fp *fuzzPeer) IsOutbound() bool { return false }
func (fp *fuzzPeer) IsPersistent() bool { return false }
func (fp *fuzzPeer) CloseConn() error { return nil }
func (fp *fuzzPeer) NodeInfo() p2p.NodeInfo { return defaultNodeInfo }
func (fp *fuzzPeer) Status() p2p.ConnectionStatus { var cs p2p.ConnectionStatus; return cs }
func (fp *fuzzPeer) SocketAddr() *p2p.NetAddress { return p2p.NewNetAddress(fp.ID(), fp.RemoteAddr()) }
func (fp *fuzzPeer) Send(byte, []byte) bool { return true }
func (fp *fuzzPeer) TrySend(byte, []byte) bool { return true }
func (fp *fuzzPeer) Set(key string, value interface{}) { fp.m[key] = value }
func (fp *fuzzPeer) Get(key string) interface{} { return fp.m[key] }
File diff suppressed because it is too large Load Diff
@@ -1,47 +0,0 @@
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, 0o755); 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))
//nolint:gosec
if err := os.WriteFile(filename, []byte(datum), 0o644); err != nil {
log.Fatalf("can't write %v to %q: %v", datum, filename, err)
}
log.Printf("wrote %q", filename)
}
}
-44
View File
@@ -1,44 +0,0 @@
package handler
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"github.com/tendermint/tendermint/libs/log"
rs "github.com/tendermint/tendermint/rpc/jsonrpc/server"
types "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)
var rpcFuncMap = map[string]*rs.RPCFunc{
"c": rs.NewRPCFunc(func(s string, i int) (string, int) { return "foo", 200 }, "s,i"),
}
var mux *http.ServeMux
func init() {
mux := http.NewServeMux()
buf := new(bytes.Buffer)
lgr := log.NewTMLogger(buf)
rs.RegisterRPCFuncs(mux, rpcFuncMap, lgr)
}
func Fuzz(data []byte) int {
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)
}
recv := new(types.RPCResponse)
if err := json.Unmarshal(blob, recv); err != nil {
panic(err)
}
return 1
}
+35
View File
@@ -0,0 +1,35 @@
//go:build gofuzz || go1.18
package tests
import (
"testing"
abciclient "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
tmsync "github.com/tendermint/tendermint/libs/sync"
mempool "github.com/tendermint/tendermint/mempool"
mempoolv1 "github.com/tendermint/tendermint/mempool/v1"
)
func FuzzMempool(f *testing.F) {
app := kvstore.NewApplication()
logger := log.NewNopLogger()
mtx := new(tmsync.Mutex)
conn := abciclient.NewLocalClient(mtx, app)
err := conn.Start()
if err != nil {
panic(err)
}
cfg := config.DefaultMempoolConfig()
cfg.Broadcast = false
mp := mempoolv1.NewTxMempool(logger, cfg, conn, 0)
f.Fuzz(func(t *testing.T, data []byte) {
_ = mp.CheckTx(data, nil, mempool.TxInfo{})
})
}
@@ -1,35 +1,63 @@
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/libs/async"
sc "github.com/tendermint/tendermint/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()
n, err := fooConn.Write(data)
if err != nil {
panic(err)
// Run Write in a separate goroutine because if data is greater than 1024
// bytes, each Write must be followed by Read (see io.Pipe documentation).
go func() {
// Copy data because Write modifies the slice.
dataToWrite := make([]byte, len(data))
copy(dataToWrite, data)
n, err := fooConn.Write(dataToWrite)
if err != nil {
panic(err)
}
if n < len(data) {
panic(fmt.Sprintf("wanted to write %d bytes, but %d was written", len(data), n))
}
}()
dataRead := make([]byte, len(data))
totalRead := 0
for totalRead < len(data) {
buf := make([]byte, len(data)-totalRead)
m, err := barConn.Read(buf)
if err != nil {
panic(err)
}
copy(dataRead[totalRead:], buf[:m])
totalRead += m
}
dataRead := make([]byte, n)
m, err := barConn.Read(dataRead)
if err != nil {
panic(err)
if !bytes.Equal(data, dataRead) {
panic("bytes written != read")
}
if !bytes.Equal(data[:n], dataRead[:m]) {
panic(fmt.Sprintf("bytes written %X != read %X", data[:n], dataRead[:m]))
}
return 1
}
type kvstoreConn struct {
@@ -0,0 +1,71 @@
//go:build gofuzz || go1.18
package tests
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/tendermint/tendermint/libs/log"
rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)
func FuzzRPCJSONRPCServer(f *testing.F) {
type args struct {
S string `json:"s"`
I int `json:"i"`
}
var rpcFuncMap = map[string]*rpcserver.RPCFunc{
"c": rpcserver.NewRPCFunc(func(ctx *rpctypes.Context, args *args) (string, error) {
return "foo", nil
}, "args"),
}
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 []rpctypes.RPCResponse
if err := json.Unmarshal(blob, &recv); err != nil {
panic(err)
}
return
}
var recv rpctypes.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("")