-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a go-fuzz entrypoint for fuzzing datastore transactions for crashes.
- Loading branch information
Showing
11 changed files
with
848 additions
and
17 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 @@ | ||
provider_*.go | ||
*.zip | ||
corpus | ||
crashers | ||
suppressions |
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,28 @@ | ||
IPFS Datastore Fuzzer | ||
==== | ||
|
||
The fuzzer provides a [go fuzzer](https://github.com/dvyukov/go-fuzz) interface | ||
to Datastore implementations. This can be used for fuzz testing of these | ||
implementations. | ||
|
||
Usage | ||
---- | ||
|
||
First, configure the datastores to fuzz (from this directory): | ||
```golang | ||
// either run via `go run` | ||
go run ./cmd/generate github.com/ipfs/go-ds-badger | ||
// or `go generate` | ||
DS_PROVIDERS="github.com/ipfs/go-ds-badger" go generate | ||
``` | ||
|
||
Then, build the fuzzing artifact and fuzz: | ||
```golang | ||
go-fuzz-build | ||
go-fuzz | ||
``` | ||
|
||
If you don't have `go-fuzz` installed, it can be acquired as: | ||
``` | ||
go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build | ||
``` |
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,104 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
|
||
ds "github.com/ipfs/go-datastore" | ||
fuzzer "github.com/ipfs/go-datastore/fuzz" | ||
dsq "github.com/ipfs/go-datastore/query" | ||
|
||
"github.com/spf13/pflag" | ||
) | ||
|
||
var input *string = pflag.StringP("input", "i", "", "file to read input from (stdin used if not specified)") | ||
var db1 *string = pflag.StringP("db1", "d", "badger", "database to fuzz") | ||
var db2 *string = pflag.StringP("db2", "e", "level", "database to fuzz") | ||
var dbFile *string = pflag.StringP("file", "f", "tmp", "where the db instances should live on disk") | ||
var threads *int = pflag.IntP("threads", "t", 1, "concurrent threads") | ||
|
||
func main() { | ||
pflag.Parse() | ||
|
||
// do one, then the other, then compare state. | ||
|
||
fuzzer.Threads = *threads | ||
|
||
var dat []byte | ||
var err error | ||
if *input == "" { | ||
dat, err = ioutil.ReadAll(os.Stdin) | ||
} else { | ||
dat, err = ioutil.ReadFile(*input) | ||
} | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Could not read %s: %v\n", *input, err) | ||
return | ||
} | ||
|
||
db1loc := *dbFile + "1" | ||
inst1, err := fuzzer.Open(*db1, db1loc, false) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Could not open db: %v\n", err) | ||
return | ||
} | ||
defer inst1.Cancel() | ||
|
||
db2loc := *dbFile + "2" | ||
inst2, err := fuzzer.Open(*db2, db2loc, false) | ||
if err != nil { | ||
inst1.Cancel() | ||
fmt.Fprintf(os.Stderr, "Could not open db: %v\n", err) | ||
return | ||
} | ||
defer inst2.Cancel() | ||
|
||
fmt.Printf("Running db1.........") | ||
inst1.Fuzz(dat) | ||
fmt.Printf("done\n") | ||
fmt.Printf("Running db2.........") | ||
inst2.Fuzz(dat) | ||
fmt.Printf("done\n") | ||
|
||
fmt.Printf("Checking equality...") | ||
db1 := inst1.DB() | ||
db2 := inst2.DB() | ||
r1, err := db1.Query(dsq.Query{}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
for r := range r1.Next() { | ||
if r.Error != nil { | ||
break | ||
} | ||
if r.Entry.Key == "/" { | ||
continue | ||
} | ||
|
||
if exist, _ := db2.Has(ds.NewKey(r.Entry.Key)); !exist { | ||
fmt.Fprintf(os.Stderr, "db2 failed to get key %s held by db1\n", r.Entry.Key) | ||
} | ||
} | ||
|
||
r2, err := db2.Query(dsq.Query{}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
for r := range r2.Next() { | ||
if r.Error != nil { | ||
break | ||
} | ||
if r.Entry.Key == "/" { | ||
continue | ||
} | ||
|
||
if exist, _ := db1.Has(ds.NewKey(r.Entry.Key)); !exist { | ||
fmt.Fprintf(os.Stderr, "db1 failed to get key %s held by db2\n", r.Entry.Key) | ||
} | ||
} | ||
|
||
fmt.Printf("Done\n") | ||
} |
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,75 @@ | ||
// This file is invoked by `go generate` | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
"text/template" | ||
) | ||
|
||
// This program generates bindings to fuzz a concrete datastore implementation. | ||
// It can be invoked by running `go generate <implemenation>` | ||
|
||
func main() { | ||
providers := os.Args[1:] | ||
|
||
if len(providers) == 0 { | ||
providers = strings.Split(os.Getenv("DS_PROVIDERS"), ",") | ||
} | ||
if len(providers) == 0 { | ||
fmt.Fprintf(os.Stderr, "No providers specified to generate. Nothing to do.") | ||
return | ||
} | ||
|
||
for _, provider := range providers { | ||
provider = strings.TrimSpace(provider) | ||
if len(provider) == 0 { | ||
continue | ||
} | ||
cmd := exec.Command("go", "get", provider) | ||
err := cmd.Run() | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to add dependency for %s: %v\n", provider, err) | ||
os.Exit(1) | ||
} | ||
|
||
nameComponents := strings.Split(provider, "/") | ||
name := nameComponents[len(nameComponents)-1] | ||
f, err := os.Create(fmt.Sprintf("provider_%s.go", name)) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to create provider file: %v\n", err) | ||
os.Exit(1) | ||
} | ||
defer f.Close() | ||
err = provideTemplate.Execute(f, struct { | ||
Package string | ||
PackageName string | ||
}{ | ||
Package: provider, | ||
PackageName: name, | ||
}) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to write provider: %v\n", err) | ||
os.Exit(1) | ||
} | ||
} | ||
} | ||
|
||
var provideTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. | ||
package fuzzer | ||
import prov "{{ .Package }}" | ||
import ds "github.com/ipfs/go-datastore" | ||
func init() { | ||
AddOpener("{{ .PackageName }}", func(loc string) ds.TxnDatastore { | ||
d, err := prov.NewDatastore(loc, nil) | ||
if err != nil { | ||
panic("could not create db instance") | ||
} | ||
return d | ||
}) | ||
} | ||
`)) |
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,115 @@ | ||
package main | ||
|
||
// Checks if a db instance is equivalent to some prefix of an input script. | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
|
||
ds "github.com/ipfs/go-datastore" | ||
fuzzer "github.com/ipfs/go-datastore/fuzz" | ||
dsq "github.com/ipfs/go-datastore/query" | ||
|
||
"github.com/spf13/pflag" | ||
) | ||
|
||
var input *string = pflag.StringP("input", "i", "", "file to read input from (stdin used if not specified)") | ||
var db *string = pflag.StringP("db", "d", "go-ds-badger", "database driver") | ||
var dbPrev *string = pflag.StringP("exist", "e", "tmp1", "database instance already made") | ||
var dbFile *string = pflag.StringP("file", "f", "tmp2", "where the replay should live") | ||
var threads *int = pflag.IntP("threads", "t", 1, "concurrent threads") | ||
|
||
type validatingReader struct { | ||
b []byte | ||
i int | ||
validator func(bool) bool | ||
validI int | ||
} | ||
|
||
func (v *validatingReader) Read(buf []byte) (n int, err error) { | ||
if v.i == len(v.b) { | ||
return 0, nil | ||
} | ||
if v.validator(false) { | ||
v.validI = v.i | ||
} | ||
buf[0] = v.b[v.i] | ||
v.i++ | ||
return 1, nil | ||
} | ||
|
||
func main() { | ||
pflag.Parse() | ||
|
||
fuzzer.Threads = *threads | ||
|
||
var dat []byte | ||
var err error | ||
if *input == "" { | ||
dat, err = ioutil.ReadAll(os.Stdin) | ||
} else { | ||
dat, err = ioutil.ReadFile(*input) | ||
} | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "could not read %s: %v\n", *input, err) | ||
return | ||
} | ||
|
||
previousDB, err := fuzzer.Open(*db, *dbPrev, false) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "could not open: %v\n", err) | ||
return | ||
} | ||
defer previousDB.Cancel() | ||
|
||
replayDB, err := fuzzer.Open(*db, *dbFile, true) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "could not open: %v\n", err) | ||
return | ||
} | ||
defer replayDB.Cancel() | ||
|
||
reader := validatingReader{dat, 0, func(verbose bool) bool { | ||
res, _ := replayDB.DB().Query(dsq.Query{}) | ||
for e := range res.Next() { | ||
if e.Entry.Key == "/" { | ||
continue | ||
} | ||
if h, _ := previousDB.DB().Has(ds.NewKey(e.Entry.Key)); !h { | ||
if verbose { | ||
fmt.Printf("failed - script run db has %s not in existing.\n", e.Entry.Key) | ||
} | ||
return false // not yet complete | ||
} | ||
} | ||
// next; make sure the other way is equal. | ||
res, _ = previousDB.DB().Query(dsq.Query{}) | ||
for e := range res.Next() { | ||
if e.Entry.Key == "/" { | ||
continue | ||
} | ||
if h, _ := replayDB.DB().Has(ds.NewKey(e.Entry.Key)); !h { | ||
if verbose { | ||
fmt.Printf("failed - existing db has %s not in replay.\n", e.Entry.Key) | ||
} | ||
return false | ||
} | ||
} | ||
// db images are the same. | ||
return true | ||
}, -1} | ||
|
||
replayDB.FuzzStream(&reader) | ||
if reader.validator(true) { | ||
reader.validI = reader.i | ||
} | ||
|
||
if reader.validI > -1 { | ||
fmt.Printf("Matched at stream position %d.\n", reader.validI) | ||
os.Exit(0) | ||
} else { | ||
fmt.Printf("Failed to match\n") | ||
os.Exit(1) | ||
} | ||
} |
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,41 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
|
||
fuzzer "github.com/ipfs/go-datastore/fuzz" | ||
|
||
"github.com/spf13/pflag" | ||
) | ||
|
||
var input *string = pflag.StringP("input", "i", "", "file to read input from (stdin used if not specified)") | ||
var db *string = pflag.StringP("database", "d", "go-ds-badger", "database to fuzz") | ||
var dbFile *string = pflag.StringP("file", "f", "tmp", "where the db instace should live on disk") | ||
var threads *int = pflag.IntP("threads", "t", 1, "concurrent threads") | ||
|
||
func main() { | ||
pflag.Parse() | ||
|
||
fuzzer.Threads = *threads | ||
|
||
if *input != "" { | ||
dat, err := ioutil.ReadFile(*input) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "could not read %s: %v\n", *input, err) | ||
os.Exit(1) | ||
} | ||
ret := fuzzer.FuzzDB(*db, *dbFile, false, dat) | ||
os.Exit(ret) | ||
} else { | ||
reader := bufio.NewReader(os.Stdin) | ||
err := fuzzer.FuzzStream(*db, *dbFile, false, reader) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Error fuzzing: %v\n", err) | ||
os.Exit(1) | ||
} | ||
return | ||
} | ||
} |
Oops, something went wrong.