-
Notifications
You must be signed in to change notification settings - Fork 115
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
go/consensus: Add fuzzing for transaction methods
- Loading branch information
Showing
9 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
corpus/ | ||
crashers/ | ||
suppressions/ | ||
*.zip | ||
gencorpus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters