Skip to content

Commit

Permalink
go/storage: Add backend api fuzzing
Browse files Browse the repository at this point in the history
  • Loading branch information
jberci committed Dec 23, 2019
1 parent 778a324 commit f3d0cb1
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 11 deletions.
4 changes: 4 additions & 0 deletions .changelog/2246.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add storage backend fuzzing.

Based on the work done for consensus fuzzing, support was added to run fuzzing
jobs on the storage api backend.
30 changes: 19 additions & 11 deletions go/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,30 @@ integrationrunner:
@$(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"
build-fuzz: consensus/tendermint/fuzz/fuzz-fuzz.zip storage/fuzz/fuzz-fuzz.zip
%/fuzz-fuzz.zip: .FORCE
@echo "Building $@"
@cd "$$(dirname "$@")"; go-fuzz-build
@cd "$$(dirname "$@")/gencorpus"; env -u GOPATH $(OASIS_GO) build -tags gofuzz

# Run fuzzing.
define canned-fuzz-run
set -x; cd "$<"; \
if ! [ -d corpus ]; then \
mkdir corpus; \
pushd corpus; \
../gencorpus/gencorpus; \
popd; \
fi; \
go-fuzz -bin=./fuzz-fuzz.zip
endef
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
$(canned-fuzz-run)
fuzz-storage: storage/fuzz/ oasis-node/oasis-node
@mkdir -p /tmp/oasis-node-fuzz-storage/identity
@chmod 0700 /tmp/oasis-node-fuzz-storage/identity
@oasis-node/oasis-node identity init --datadir /tmp/oasis-node-fuzz-storage/identity
$(canned-fuzz-run)

# Clean.
clean:
Expand Down
129 changes: 129 additions & 0 deletions go/common/fuzz/fuzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package fuzz

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

Expand Down Expand Up @@ -103,3 +105,130 @@ func MakeSampleBlob(typ interface{}) []byte {

return source.GetTraceback()
}

// InterfaceFuzzer is a helper class for fuzzing methods in structs or interfaces.
type InterfaceFuzzer struct {
instance interface{}

typeObject reflect.Type
valObject reflect.Value

methodList []int

typeOverrides map[string]func()interface{}
}

// OverrideType registers a custom callback for creating instances of a given type.
func (i *InterfaceFuzzer) OverrideType(typeName string, factory func()interface{}) {
i.typeOverrides[typeName] = factory
}

// DispatchBlob constructs a method call with arguments from the given blob and dispatches it.
func (i *InterfaceFuzzer) DispatchBlob(blob []byte) ([]reflect.Value, bool) {
if len(blob) < 1 {
return nil, false
}

meth := int(blob[0])
if meth >= len(i.methodList) {
return nil, false
}
meth = i.methodList[meth]
if meth >= i.typeObject.NumMethod() {
return nil, false
}
methType := i.typeObject.Method(meth).Type
method := i.valObject.Method(meth)

source := NewRandSource(blob[1:])
fuzzer := gofuzz.New()
fuzzer = fuzzer.RandSource(source).NilChance(0)

in := []reflect.Value{}

for arg := 1; arg < methType.NumIn(); arg++ {
inType := methType.In(arg)
inTypeName := fmt.Sprintf("%s.%s", inType.PkgPath(), inType.Name())

var val reflect.Value
if factory, ok := i.typeOverrides[inTypeName]; ok {
inst := factory()
val = reflect.ValueOf(inst)
} else {
val = reflect.New(inType)
if val.Interface() != nil {
fuzzer.Fuzz(val.Interface())
}
val = val.Elem()
}
in = append(in, val)
}

return method.Call(in), true
}

// MakeSampleBlobs returns an array of sample blobs for all methods in the interface.
func (i *InterfaceFuzzer) MakeSampleBlobs() [][]byte {
blobList := [][]byte{}
for seq, meth := range i.methodList {
source := NewTrackingRandSource()
fuzzer := gofuzz.New()
fuzzer = fuzzer.RandSource(source).NilChance(0)

method := i.typeObject.Method(meth)
blob := []byte{byte(seq)}
for arg := 1; arg < method.Type.NumIn(); arg++ {
inType := method.Type.In(arg)
inTypeName := fmt.Sprintf("%s.%s", inType.PkgPath(), inType.Name())
if _, ok := i.typeOverrides[inTypeName]; !ok {
newValue := reflect.New(inType)
if newValue.Interface() != nil {
fuzzer.Fuzz(newValue.Interface())
}
}
}

blob = append(blob, source.GetTraceback()...)
blobList = append(blobList, blob)
}

return blobList
}

// Method returns the method object associated with the fuzzer's index-th method for this instance.
func (i *InterfaceFuzzer) Method(method int) reflect.Method {
return i.typeObject.Method(i.methodList[method])
}

// IgnoreMethodNames makes the interface fuzzer skip the named methods.
func (i *InterfaceFuzzer) IgnoreMethodNames(names []string) {
for _, name := range names {
for listIndex, methIndex := range i.methodList {
if i.typeObject.Method(methIndex).Name == name {
i.methodList = append(i.methodList[:listIndex], i.methodList[listIndex+1:]...)
break
}
}
}
}

// NewInterfaceFuzzer creates a new InterfaceFuzzer for the given instance.
func NewInterfaceFuzzer(instance interface{}) *InterfaceFuzzer {
val := reflect.ValueOf(instance)
ret := &InterfaceFuzzer{
instance: instance,
typeObject: val.Type(),
valObject: val,
typeOverrides: map[string]func()interface{}{
"context.Context": func()interface{}{
return context.Background()
},
},
}

for meth := 0; meth < val.NumMethod(); meth++ {
ret.methodList = append(ret.methodList, meth)
}

return ret
}
5 changes: 5 additions & 0 deletions go/storage/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
corpus/
crashers/
suppressions/
*.zip
gencorpus/gencorpus
62 changes: 62 additions & 0 deletions go/storage/fuzz/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// +build gofuzz

package fuzz

import (
"context"
"io/ioutil"

"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/common/crypto/signature/signers/file"
commonFuzz "github.com/oasislabs/oasis-core/go/common/fuzz"
"github.com/oasislabs/oasis-core/go/common/identity"
"github.com/oasislabs/oasis-core/go/storage"
"github.com/oasislabs/oasis-core/go/storage/api"
)

const (
dataDir string = "/tmp/oasis-node-fuzz-storage"
identityDir string = dataDir + "/identity"
)

var (
storageBackend api.Backend

fuzzer *commonFuzz.InterfaceFuzzer
)

func init() {
signerFactory := file.NewFactory(identityDir, signature.SignerNode, signature.SignerP2P, signature.SignerConsensus)
identity, err := identity.Load(identityDir, signerFactory)
if err != nil {
panic(err)
}

// Every Fuzz invocation should get its own database,
// otherwise the database handles would clash.
localDB, err := ioutil.TempDir(dataDir, "worker")
if err != nil {
panic(err)
}

// Create the storage backend service.
storageBackend, err = storage.New(context.Background(), localDB, identity, nil, nil)
if err != nil {
panic(err)
}

// Create and prepare the fuzzer.
fuzzer = commonFuzz.NewInterfaceFuzzer(storageBackend)
fuzzer.IgnoreMethodNames([]string{
"Cleanup",
"Initialized",
})
}

func Fuzz(data []byte) int {
<-storageBackend.Initialized()

fuzzer.DispatchBlob(data)

return 0
}
39 changes: 39 additions & 0 deletions go/storage/fuzz/gencorpus/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// +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 (
"context"
"fmt"
"io/ioutil"

commonFuzz "github.com/oasislabs/oasis-core/go/common/fuzz"
"github.com/oasislabs/oasis-core/go/common/identity"
"github.com/oasislabs/oasis-core/go/storage"
)

const (
samplesPerMethod int = 20
)

func main() {
storage, err := storage.New(context.Background(), "/tmp/oasis-node-fuzz-storage", &identity.Identity{}, nil, nil)
if err != nil {
panic(err)
}
fuzzer := commonFuzz.NewInterfaceFuzzer(storage)
fuzzer.IgnoreMethodNames([]string{
"Cleanup",
"Initialized",
})

for i := 0; i < samplesPerMethod; i++ {
blobs := fuzzer.MakeSampleBlobs()
for meth := 0; meth < len(blobs); meth++ {
fileName := fmt.Sprintf("%s_%02d.bin", fuzzer.Method(meth).Name, i)
_ = ioutil.WriteFile(fileName, blobs[meth], 0644)
}
}
}

0 comments on commit f3d0cb1

Please sign in to comment.