Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests/fuzzers: add snap protocol handling fuzzer #23957

Merged
merged 1 commit into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
tests/fuzzzers. eth/protocols/snap: add snap protocol fuzzers
  • Loading branch information
holiman committed Nov 30, 2021
commit e95f23ad2673048345736b73279d036eda4ac07a
6 changes: 3 additions & 3 deletions eth/protocols/snap/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions eth/protocols/snap/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions oss-fuzz.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 39 additions & 0 deletions tests/fuzzers/snap/debug/main.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

package main
holiman marked this conversation as resolved.
Show resolved Hide resolved

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 <file>\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)
}
164 changes: 164 additions & 0 deletions tests/fuzzers/snap/fuzz_handler.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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)
}