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 6, 2019
1 parent bf85e38 commit 3e96ced
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 0 deletions.
18 changes: 18 additions & 0 deletions go/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ urkel-test-helpers: generate
test-vectors/staking: generate
@env -u GOPATH $(OASIS_GO) run ./staking/gen_vectors

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

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

# Clean.
clean:
@env -u GOPATH $(OASIS_GO) clean
Expand Down
68 changes: 68 additions & 0 deletions go/common/fuzz/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// +build gofuzz

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

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

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

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,
}
}
25 changes: 25 additions & 0 deletions go/consensus/tendermint/abci/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,12 @@ func (mux *abciMux) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginB
return response
}

func (mux *abciMux) TestExecuteTx(tx []byte) error {
ctx := NewContext(ContextCheckTx, mux.currentTime, mux.state)

return mux.executeTx(ctx, tx)
}

func (mux *abciMux) executeTx(ctx *Context, rawTx []byte) error {
logger := mux.logger.With("is_check_only", ctx.IsCheckOnly())

Expand Down Expand Up @@ -800,6 +806,10 @@ func (mux *abciMux) doCleanup() {
}
}

func (mux *abciMux) TestRegister(app Application) error {
return mux.doRegister(app)
}

func (mux *abciMux) doRegister(app Application) error {
name := app.Name()
if mux.appsByName[name] != nil {
Expand Down Expand Up @@ -859,6 +869,17 @@ func (mux *abciMux) checkDependencies() error {
return nil
}

func (mux *abciMux) GetApplicationState() *ApplicationState {
return mux.state
}

func NewTestMuxer(ctx context.Context, cfg *ApplicationConfig) (*abciMux, func(), error) { // nolint
mux, err := newABCIMux(ctx, cfg)
return mux, func() {
mux.doCleanup()
}, err
}

func newABCIMux(ctx context.Context, cfg *ApplicationConfig) (*abciMux, error) {
state, err := newApplicationState(ctx, cfg)
if err != nil {
Expand Down Expand Up @@ -1135,6 +1156,10 @@ func (s *ApplicationState) metricsWorker() {
}
}

func NewApplicationStateTesting(ctx context.Context, cfg *ApplicationConfig) (*ApplicationState, error) {
return newApplicationState(ctx, cfg)
}

func newApplicationState(ctx context.Context, cfg *ApplicationConfig) (*ApplicationState, error) {
db, err := db.New(filepath.Join(cfg.DataDir, "abci-mux-state"), false)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions go/consensus/tendermint/apps/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
corpus/
crashers/
suppressions/
*.zip
gencorpus
223 changes: 223 additions & 0 deletions go/consensus/tendermint/apps/fuzz/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// +build gofuzz

package fuzz

import (
"context"
"fmt"
"time"

gofuzz "github.com/google/gofuzz"

"github.com/oasislabs/oasis-core/go/common/cbor"
"github.com/oasislabs/oasis-core/go/common/entity"
"github.com/oasislabs/oasis-core/go/common/fuzz"
"github.com/oasislabs/oasis-core/go/common/node"
"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"
//roothashApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/roothash"
stakingApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking"
epochtime "github.com/oasislabs/oasis-core/go/epochtime/api"
registry "github.com/oasislabs/oasis-core/go/registry/api"
//roothash "github.com/oasislabs/oasis-core/go/roothash/api"
staking "github.com/oasislabs/oasis-core/go/staking/api"
)

var (
// Fuzzy is the list of all apps using the consensus interfaces and transactions.
Fuzzy []App = []App{
App{ // epochtime_mock
Instance: epochtimemock.New(),
Methods: []Method{
Method{
Name: "SetEpoch",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var call epochtime.EpochTime
fuzzer.Fuzz(&call)
return &call
},
},
},
},
App{ // registry
Instance: registryApp.New(),
Methods: []Method{
Method{
Name: "RegisterEntity",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var signed entity.SignedEntity
fuzzer.Fuzz(&signed)
return &signed
},
},
Method{
Name: "DeregisterEntity",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
return nil
},
},
Method{
Name: "RegisterNode",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var signed node.SignedNode
fuzzer.Fuzz(&signed)
return &signed
},
},
Method{
Name: "DeregisterNode",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
return nil
},
},
Method{
Name: "UnfreezeNode",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var unfreeze registry.UnfreezeNode
fuzzer.Fuzz(&unfreeze)
return &unfreeze
},
},
Method{
Name: "RegisterRuntime",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var runtime registry.SignedRuntime
fuzzer.Fuzz(&runtime)
return &runtime
},
},
},
},
/*App{ // roothash
Instance: roothashApp.New(),
Methods: []Method{
Method{
Name: "ComputeCommit",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var cc roothash.ComputeCommit
fuzzer.Fuzz(&cc)
return &cc
},
},
Method{
Name: "MergeCommit",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var mc roothash.MergeCommit
fuzzer.Fuzz(&mc)
return &mc
},
},
},
},*/
App{ // staking
Instance: stakingApp.New(),
Methods: []Method{
Method{
Name: "Transfer",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var t staking.Transfer
fuzzer.Fuzz(&t)
return &t
},
},
Method{
Name: "Burn",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var b staking.Burn
fuzzer.Fuzz(&b)
return &b
},
},
Method{
Name: "AddEscrow",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var ae staking.Escrow
fuzzer.Fuzz(&ae)
return &ae
},
},
Method{
Name: "ReclaimEscrow",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var re staking.ReclaimEscrow
fuzzer.Fuzz(&re)
return &re
},
},
Method{
Name: "AmendCommissionSchedule",
TxFunc: func(fuzzer *gofuzz.Fuzzer) interface{} {
var acs staking.AmendCommissionSchedule
fuzzer.Fuzz(&acs)
return &acs
},
},
},
},
}
)

// Method describes a single fuzzable method for an app on the consensus interface.
type Method struct {
// Name is the name of the transaction; it should be the same as in the transaction envelope.
Name transaction.MethodName
// TxFunc fills out a transaction body for this method.
TxFunc func(*gofuzz.Fuzzer)interface{}
}

// App describes an app on the consensus interface that has fuzzable methods.
type App struct {
// Instance is an instance of the consensus application.
Instance abci.Application
// Methods is an array of fuzzable methods that this app contains.
Methods []Method
}

func Fuzz(data []byte) int {
source := fuzz.NewRandSource(data)
fuzzer := gofuzz.New()
fuzzer = fuzzer.RandSource(source)

ctx := context.Background()

var pruneCfg abci.PruneConfig

appConfig := &abci.ApplicationConfig{
DataDir: "/tmp/fuzzytest",
Pruning: pruneCfg,
HaltEpochHeight: 1000000,
MinGasPrice: 1,
}

muxer, closer, _ := abci.NewTestMuxer(ctx, appConfig)
defer closer()
abciCtx := abci.NewContext(abci.ContextCheckTx, time.Now(), muxer.GetApplicationState())

if len(data) < 3 {
return -1
}

app := int(data[0])
meth := int(data[1])

if app >= len(Fuzzy) || meth >= len(Fuzzy[app].Methods) {
return -1
}

method := &Fuzzy[app].Methods[meth]

obj := method.TxFunc(fuzzer)
tx := &transaction.Transaction{
Method: method.Name,
Body: cbor.Marshal(obj),
}

err := Fuzzy[app].Instance.ExecuteTx(abciCtx, tx)
if err != nil {
fmt.Println("error in app", err)
}

return 0
}
Loading

0 comments on commit 3e96ced

Please sign in to comment.