diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go
index 178230995..0a1ee2637 100644
--- a/eth/protocols/snap/handler.go
+++ b/eth/protocols/snap/handler.go
@@ -120,17 +120,17 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol {
// When this function terminates, the peer is disconnected.
func Handle(backend Backend, peer *Peer) error {
for {
- if err := handleMessage(backend, peer); err != nil {
+ if err := HandleMessage(backend, peer); err != nil {
peer.Log().Debug("Message handling failed in `snap`", "err", err)
return err
}
}
}
-// handleMessage is invoked whenever an inbound message is received from a
+// HandleMessage is invoked whenever an inbound message is received from a
// remote peer on the `snap` protocol. The remote connection is torn down upon
// returning any error.
-func handleMessage(backend Backend, peer *Peer) error {
+func HandleMessage(backend Backend, peer *Peer) error {
// Read the next message from the remote peer, and ensure it's fully consumed
msg, err := peer.rw.ReadMsg()
if err != nil {
diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go
index fc721c9f9..87a62d2f8 100644
--- a/eth/protocols/snap/peer.go
+++ b/eth/protocols/snap/peer.go
@@ -46,6 +46,16 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
}
}
+// NewFakePeer create a fake snap peer without a backing p2p peer, for testing purposes.
+func NewFakePeer(version uint, id string, rw p2p.MsgReadWriter) *Peer {
+ return &Peer{
+ id: id,
+ rw: rw,
+ version: version,
+ logger: log.New("peer", id[:8]),
+ }
+}
+
// ID retrieves the peer's unique identifier.
func (p *Peer) ID() string {
return p.id
diff --git a/oss-fuzz.sh b/oss-fuzz.sh
index 9a24f6b17..745a5ba7c 100644
--- a/oss-fuzz.sh
+++ b/oss-fuzz.sh
@@ -120,5 +120,10 @@ compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1MultiExp fuzz_cross_g1_multiex
compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG2Add fuzz_cross_g2_add
compile_fuzzer tests/fuzzers/bls12381 FuzzCrossPairing fuzz_cross_pairing
+compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range
+compile_fuzzer tests/fuzzers/snap FuzzSRange fuzz_storage_range
+compile_fuzzer tests/fuzzers/snap FuzzByteCodes fuzz_byte_codes
+compile_fuzzer tests/fuzzers/snap FuzzTrieNodes fuzz_trie_nodes
+
#TODO: move this to tests/fuzzers, if possible
compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b
diff --git a/tests/fuzzers/snap/debug/main.go b/tests/fuzzers/snap/debug/main.go
new file mode 100644
index 000000000..d0d1b4930
--- /dev/null
+++ b/tests/fuzzers/snap/debug/main.go
@@ -0,0 +1,39 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ "github.com/ethereum/go-ethereum/tests/fuzzers/snap"
+)
+
+func main() {
+ if len(os.Args) != 2 {
+ fmt.Fprintf(os.Stderr, "Usage: debug \n")
+ os.Exit(1)
+ }
+ crasher := os.Args[1]
+ data, err := ioutil.ReadFile(crasher)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err)
+ os.Exit(1)
+ }
+ snap.FuzzTrieNodes(data)
+}
diff --git a/tests/fuzzers/snap/fuzz_handler.go b/tests/fuzzers/snap/fuzz_handler.go
new file mode 100644
index 000000000..1ae61df29
--- /dev/null
+++ b/tests/fuzzers/snap/fuzz_handler.go
@@ -0,0 +1,164 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package snap
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/protocols/snap"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/p2p/enode"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ fuzz "github.com/google/gofuzz"
+)
+
+var trieRoot common.Hash
+
+func getChain() *core.BlockChain {
+ db := rawdb.NewMemoryDatabase()
+ ga := make(core.GenesisAlloc, 1000)
+ var a = make([]byte, 20)
+ var mkStorage = func(k, v int) (common.Hash, common.Hash) {
+ var kB = make([]byte, 32)
+ var vB = make([]byte, 32)
+ binary.LittleEndian.PutUint64(kB, uint64(k))
+ binary.LittleEndian.PutUint64(vB, uint64(v))
+ return common.BytesToHash(kB), common.BytesToHash(vB)
+ }
+ storage := make(map[common.Hash]common.Hash)
+ for i := 0; i < 10; i++ {
+ k, v := mkStorage(i, i)
+ storage[k] = v
+ }
+ for i := 0; i < 1000; i++ {
+ binary.LittleEndian.PutUint64(a, uint64(i+0xff))
+ acc := core.GenesisAccount{Balance: big.NewInt(int64(i))}
+ if i%2 == 1 {
+ acc.Storage = storage
+ }
+ ga[common.BytesToAddress(a)] = acc
+ }
+ gspec := core.Genesis{
+ Config: params.TestChainConfig,
+ Alloc: ga,
+ }
+ genesis := gspec.MustCommit(db)
+ blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 2,
+ func(i int, gen *core.BlockGen) {})
+ cacheConf := &core.CacheConfig{
+ TrieCleanLimit: 0,
+ TrieDirtyLimit: 0,
+ TrieTimeLimit: 5 * time.Minute,
+ TrieCleanNoPrefetch: true,
+ TrieCleanRejournal: 0,
+ SnapshotLimit: 100,
+ SnapshotWait: true,
+ }
+ trieRoot = blocks[len(blocks)-1].Root()
+ bc, _ := core.NewBlockChain(db, cacheConf, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
+ if _, err := bc.InsertChain(blocks); err != nil {
+ panic(err)
+ }
+ return bc
+}
+
+type dummyBackend struct {
+ chain *core.BlockChain
+}
+
+func (d *dummyBackend) Chain() *core.BlockChain { return d.chain }
+func (d *dummyBackend) RunPeer(*snap.Peer, snap.Handler) error { return nil }
+func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" }
+func (d *dummyBackend) Handle(*snap.Peer, snap.Packet) error { return nil }
+
+type dummyRW struct {
+ code uint64
+ data []byte
+ writeCount int
+}
+
+func (d *dummyRW) ReadMsg() (p2p.Msg, error) {
+ return p2p.Msg{
+ Code: d.code,
+ Payload: bytes.NewReader(d.data),
+ ReceivedAt: time.Now(),
+ Size: uint32(len(d.data)),
+ }, nil
+}
+
+func (d *dummyRW) WriteMsg(msg p2p.Msg) error {
+ d.writeCount++
+ return nil
+}
+
+func doFuzz(input []byte, obj interface{}, code int) int {
+ if len(input) > 1024*4 {
+ return -1
+ }
+ bc := getChain()
+ defer bc.Stop()
+ backend := &dummyBackend{bc}
+ fuzz.NewFromGoFuzz(input).Fuzz(obj)
+ var data []byte
+ switch p := obj.(type) {
+ case *snap.GetTrieNodesPacket:
+ p.Root = trieRoot
+ data, _ = rlp.EncodeToBytes(obj)
+ default:
+ data, _ = rlp.EncodeToBytes(obj)
+ }
+ cli := &dummyRW{
+ code: uint64(code),
+ data: data,
+ }
+ peer := snap.NewFakePeer(65, "gazonk01", cli)
+ err := snap.HandleMessage(backend, peer)
+ switch {
+ case err == nil && cli.writeCount != 1:
+ panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount))
+ case err != nil && cli.writeCount != 0:
+ panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount))
+ }
+ return 1
+}
+
+// To run a fuzzer, do
+// $ CGO_ENABLED=0 go-fuzz-build -func FuzzTrieNodes
+// $ go-fuzz
+
+func FuzzARange(input []byte) int {
+ return doFuzz(input, &snap.GetAccountRangePacket{}, snap.GetAccountRangeMsg)
+}
+func FuzzSRange(input []byte) int {
+ return doFuzz(input, &snap.GetStorageRangesPacket{}, snap.GetStorageRangesMsg)
+}
+func FuzzByteCodes(input []byte) int {
+ return doFuzz(input, &snap.GetByteCodesPacket{}, snap.GetByteCodesMsg)
+}
+func FuzzTrieNodes(input []byte) int {
+ return doFuzz(input, &snap.GetTrieNodesPacket{}, snap.GetTrieNodesMsg)
+}