-
Notifications
You must be signed in to change notification settings - Fork 113
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
306 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
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. |
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,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() | ||
} |
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,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 | ||
} |
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/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,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 | ||
} |
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,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) | ||
} | ||
} | ||
} |
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