Skip to content

Commit

Permalink
go/consensus: Add fuzzing for transaction methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jberci committed Dec 23, 2019
1 parent 6dd7628 commit 778a324
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changelog/2245.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Add fuzzing for consensus methods.

Initial support for fuzzing was added, along with an implementation of
it for some of the consensus methods. The implementation uses
oasis-core's demultiplexing and method dispatch mechanisms.
20 changes: 20 additions & 0 deletions go/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ integrationrunner:
@$(ECHO) "$(CYAN)*** Testing oasis-node with coverate...$(OFF)"
@$(GO) test $(GOFLAGS) -c -covermode=atomic -coverpkg=./... -o oasis-node/$@/$@.test ./oasis-node/$@

# Fuzzing binaries.
build-fuzz: consensus/tendermint/fuzz/fuzz-fuzz.zip
consensus/tendermint/fuzz/fuzz-fuzz.zip: .FORCE
@echo "Building consensus fuzzer"
@cd "$$(dirname "$@")"; go-fuzz-build
@cd "$$(dirname "$@")/gencorpus"; env -u GOPATH $(OASIS_GO) build -tags gofuzz

# Run fuzzing.
fuzz-consensus: consensus/tendermint/fuzz/
set -x; cd "$<"; \
if ! [ -d corpus ]; then \
mkdir corpus; \
pushd corpus; \
../gencorpus/gencorpus; \
popd; \
fi; \
go-fuzz -bin=./fuzz-fuzz.zip

# Clean.
clean:
@$(ECHO) "$(CYAN)*** Cleaning up Go...$(OFF)"
Expand All @@ -74,3 +92,5 @@ clean:
generate $(go-binaries) build \
$(test-helpers) build-helpers $(test-vectors) gen-test-vectors \
fmt lint test integrationrunner clean all

.FORCE:
105 changes: 105 additions & 0 deletions go/common/fuzz/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// +build gofuzz

// Package fuzz provides some common utilities useful for fuzzing other packages.
package fuzz

import (
"encoding/binary"
"math/rand"
"reflect"

gofuzz "github.com/google/gofuzz"
)

var (
_ rand.Source64 = (*Source)(nil)
)

// Source is a randomness source for the standard random generator.
type Source struct {
Backing []byte
Exhausted int

pos int
track bool

traceback []byte
}

func (s *Source) Int63() int64 {
return int64(s.Uint64()&((1<<63)-1))
}

func (s *Source) Seed(_ int64) {
// Nothing to do here.
}

func (s *Source) Uint64() uint64 {
if s.pos+8 > len(s.Backing) {
s.Exhausted += 8
r := rand.Uint64()
if s.track {
chunk := make([]byte, 8)
binary.BigEndian.PutUint64(chunk[0:], r)
s.traceback = append(s.traceback, chunk...)
}
return r
}

s.pos += 8
return binary.BigEndian.Uint64(s.Backing[s.pos-8 : s.pos])
}

// GetTraceback returns the array of bytes returned from the random generator so far.
func (s *Source) GetTraceback() []byte {
return s.traceback
}

// NewRandSource returns a new random source with the given backing array.
func NewRandSource(backing []byte) *Source {
return &Source{
Backing: backing,
}
}

// NewTrackingRandSource returns a new random source that keeps track of the bytes returned.
func NewTrackingRandSource() *Source {
return &Source{
Backing: []byte{},
track: true,
}
}

// NewFilledInstance fills the given object with random values from the given blob.
func NewFilledInstance(data []byte, typ interface{}) (interface{}, bool) {
if typ == nil {
return nil, true
}

source := NewRandSource(data)
fuzzer := gofuzz.New()
fuzzer = fuzzer.RandSource(source)

obj := reflect.New(reflect.TypeOf(typ)).Interface()

fuzzer.Fuzz(obj)

return obj, source.Exhausted == 0
}

// MakeSampleBlob creates and returns a sample blob of bytes for filling the given object.
func MakeSampleBlob(typ interface{}) []byte {
if typ == nil {
return []byte{}
}

source := NewTrackingRandSource()
fuzzer := gofuzz.New()
fuzzer = fuzzer.RandSource(source)

obj := reflect.New(reflect.TypeOf(typ)).Interface()

fuzzer.Fuzz(obj)

return source.GetTraceback()
}
34 changes: 34 additions & 0 deletions go/consensus/tendermint/abci/mux_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// +build gofuzz

package abci

import (
"context"
)

// MockABCIMux exports some of the muxer's internal methods for testing use.
type MockABCIMux struct {
*abciMux
}

// MockRegisterApp is used to register apps with this muxer during testing.
func (mux *MockABCIMux) MockRegisterApp(app Application) error {
return mux.doRegister(app)
}

// MockClose cleans up the muxer's state; it must be called once the muxer is no longer needed.
func (mux *MockABCIMux) MockClose() {
mux.doCleanup()
}

// NewMockMux creates a new ABCI mux suitable for testing.
func NewMockMux(ctx context.Context, cfg *ApplicationConfig) (*MockABCIMux, error) {
mux, err := newABCIMux(ctx, cfg)
if err != nil {
return nil, err
}
mockMux := &MockABCIMux{
abciMux: mux,
}
return mockMux, nil
}
5 changes: 5 additions & 0 deletions go/consensus/tendermint/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
corpus/
crashers/
suppressions/
*.zip
gencorpus/gencorpus
101 changes: 101 additions & 0 deletions go/consensus/tendermint/fuzz/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// +build gofuzz

package fuzz

import (
"context"

"github.com/tendermint/tendermint/abci/types"

"github.com/oasislabs/oasis-core/go/common/cbor"
"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/common/crypto/signature/signers/memory"
"github.com/oasislabs/oasis-core/go/common/fuzz"
"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/epochtime_mock"
registryApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry"
stakingApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking"
)

var (
txSigner signature.Signer = memory.NewTestSigner("consensus-fuzz")

// FuzzableApps is a list of ABCI apps the fuzzer can fuzz.
FuzzableApps []abci.Application = []abci.Application{
epochtimemock.New(),
registryApp.New(),
stakingApp.New(),
}

// FuzzableMethods is a list of all the apps' transaction methods that are fuzzable.
FuzzableMethods []transaction.MethodName
)

func init() {
for _, app := range FuzzableApps {
FuzzableMethods = append(FuzzableMethods, app.Methods()...)
}
}

func Fuzz(data []byte) int {
var err error

ctx := context.Background()

var pruneCfg abci.PruneConfig

appConfig := &abci.ApplicationConfig{
DataDir: "/tmp/oasis-node-fuzz-consensus",
Pruning: pruneCfg,
HaltEpochHeight: 1000000,
MinGasPrice: 1,
}

// The muxer will start with the previous state, if it exists (the state database isn't cleared).
muxer, _ := abci.NewMockMux(ctx, appConfig)
defer muxer.MockClose()

for _, app := range FuzzableApps {
muxer.MockRegisterApp(app)
}

if len(data) < 2 {
return -1
}
meth := int(data[0])
if meth >= len(FuzzableMethods) {
return -1
}

methodName := FuzzableMethods[meth]

blob, _ := fuzz.NewFilledInstance(data, methodName.BodyType())

tx := &transaction.Transaction{
Method: methodName,
Body: cbor.Marshal(blob),
}

signedTx, err := transaction.Sign(txSigner, tx)
if err != nil {
panic("error signing transaction")
}

txBlob := cbor.Marshal(&signedTx)

// Check the transaction.
checkReq := types.RequestCheckTx{
Tx: txBlob,
Type: types.CheckTxType_New,
}
muxer.CheckTx(checkReq)

// And try executing it too.
deliverReq := types.RequestDeliverTx{
Tx: txBlob,
}
muxer.DeliverTx(deliverReq)

return 0
}
32 changes: 32 additions & 0 deletions go/consensus/tendermint/fuzz/gencorpus/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// +build gofuzz

// Gencorpus implements a simple utility to generate corpus files for the fuzzer.
// It has no command-line options and creates the files in the current working directory.
package main

import (
"fmt"
"io/ioutil"

commonFuzz "github.com/oasislabs/oasis-core/go/common/fuzz"
appFuzz "github.com/oasislabs/oasis-core/go/consensus/tendermint/fuzz"
)

const (
samplesPerMethod int = 20
)

func main() {
for meth := 0; meth < len(appFuzz.FuzzableMethods); meth++ {
for i := 0; i < samplesPerMethod; i++ {
methodName := appFuzz.FuzzableMethods[meth]

fmt.Println("generating sample", i, "for method", methodName)

actualSample := []byte{byte(meth)}
actualSample = append(actualSample, commonFuzz.MakeSampleBlob(methodName.BodyType())...)
fileName := fmt.Sprintf("%02d_%s_%02d.bin", meth, string(methodName), i)
_ = ioutil.WriteFile(fileName, actualSample, 0644)
}
}
}
2 changes: 2 additions & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ require (
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
github.com/dgraph-io/badger/v2 v2.0.0
github.com/dvyukov/go-fuzz v0.0.0-20191022152526-8cb203812681 // indirect
github.com/eapache/channels v1.1.0
github.com/eapache/queue v1.1.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/fxamacker/cbor v1.3.2
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 // indirect
github.com/go-kit/kit v0.9.0
github.com/golang/protobuf v1.3.2
github.com/google/gofuzz v1.0.0
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
github.com/hpcloud/tail v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvyukov/go-fuzz v0.0.0-20191022152526-8cb203812681 h1:3WV5aRRj1ELP3RcLlBp/v0WJTuy47OQMkL9GIQq8QEE=
github.com/dvyukov/go-fuzz v0.0.0-20191022152526-8cb203812681/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
Expand Down

0 comments on commit 778a324

Please sign in to comment.