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 9, 2019
1 parent e568a58 commit 08cb3ca
Show file tree
Hide file tree
Showing 9 changed files with 313 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

# 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()&((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,
}
}
16 changes: 16 additions & 0 deletions go/consensus/api/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"reflect"
"strings"
"sync"

"github.com/oasislabs/oasis-core/go/common/cbor"
Expand Down Expand Up @@ -170,6 +171,11 @@ func (m MethodName) BodyType() interface{} {
return bodyType
}

// ModuleName returns the module name portion of the method name.
func (m MethodName) ModuleName() string {
return strings.Split(string(m), MethodSeparator)[0]
}

// NewMethodName creates a new method name.
//
// Module and method pair must be unique. If they are not, this method
Expand All @@ -184,3 +190,13 @@ func NewMethodName(module, method string, bodyType interface{}) MethodName {

return MethodName(name)
}

// GetRegisteredMethods returns a map of the registered method names and their body types.
func GetRegisteredMethods() map[MethodName]interface{} {
ret := make(map[MethodName]interface{})
registeredMethods.Range(func(key, value interface{}) bool {
ret[MethodName(key.(string))] = value
return true
})
return ret
}
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
135 changes: 135 additions & 0 deletions go/consensus/tendermint/apps/fuzz/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// +build gofuzz

package fuzz

import (
"context"
"fmt"
"reflect"
"sort"
"sync"
"time"

gofuzz "github.com/google/gofuzz"

"github.com/oasislabs/oasis-core/go/common/cbor"
"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"
//roothashApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/roothash"
stakingApp "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking"
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 (
genListGuard sync.Once

// Fuzzy is the list of all apps using the consensus interfaces and transactions.
Fuzzy []*App = []*App{}
)

// 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 []transaction.MethodName
}

func GetMethodList() []*App {
genListGuard.Do(func() {
methodMap := make(map[string]*App)
for k, _ := range transaction.GetRegisteredMethods() {
appName := k.ModuleName()
if app, ok := methodMap[appName]; ok {
app.Methods = append(app.Methods, k)
} else {
methodMap[appName] = &App{
Methods: []transaction.MethodName{
k,
},
}
}
}

methodMap[epochtimemock.AppName].Instance = epochtimemock.New()
methodMap[registry.ModuleName].Instance = registryApp.New()
methodMap[staking.ModuleName].Instance = stakingApp.New()
Fuzzy = []*App{
methodMap[epochtimemock.AppName],
methodMap[registry.ModuleName],
methodMap[staking.ModuleName],
}

for _, v := range Fuzzy {
sort.Slice(v.Methods, func(i, j int) bool {
return v.Methods[i] < v.Methods[j]
})
}
})

return Fuzzy
}

// TxFunc fills and returns the transaction body for the given method with the given fuzzer.
func TxFunc(appIndex, methodIndex int, fuzzer *gofuzz.Fuzzer) interface{} {
method := Fuzzy[appIndex].Methods[methodIndex]
bodyType := method.BodyType()
if bodyType == nil {
// Can't use the fuzzer for that.
return nil
}
instance := reflect.New(reflect.TypeOf(bodyType)).Interface()
fuzzer.Fuzz(instance)
return instance
}

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
}

methodName := Fuzzy[app].Methods[meth]

tx := &transaction.Transaction{
Method: methodName,
Body: cbor.Marshal(TxFunc(app, meth, fuzzer)),
}

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

return 0
}
42 changes: 42 additions & 0 deletions go/consensus/tendermint/apps/fuzz/gencorpus/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// +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"

gofuzz "github.com/google/gofuzz"

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

const (
samplesPerMethod int = 20
)

func main() {
appFuzz.GetMethodList()
for app := 0; app < len(appFuzz.Fuzzy); app++ {
for meth := 0; meth < len(appFuzz.Fuzzy[app].Methods); meth++ {
for i := 0; i < samplesPerMethod; i++ {
source := commonFuzz.NewTrackingRandSource()
fuzzer := gofuzz.New()
fuzzer.RandSource(source)

methodName := appFuzz.Fuzzy[app].Methods[meth]

fmt.Println("generating sample", i, "for method", methodName)
appFuzz.TxFunc(app, meth, fuzzer)

actualSample := []byte{byte(app), byte(meth)}
actualSample = append(actualSample, source.GetTraceback()...)
fileName := fmt.Sprintf("%02d_%02d_%s_%02d.bin", app, 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,6 +21,7 @@ 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.0.0-rc.2.0.20190625224416-e0016993b790+incompatible
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
Expand All @@ -30,6 +31,7 @@ require (
github.com/go-kit/kit v0.9.0
github.com/golang/protobuf v1.3.2
github.com/golang/snappy v0.0.1
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 @@ -78,6 +78,8 @@ github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/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 08cb3ca

Please sign in to comment.