Skip to content

Commit 2391fbc

Browse files
authored
tests/fuzzers: move fuzzers into native packages (ethereum#28467)
This PR moves our fuzzers from tests/fuzzers into whatever their respective 'native' package is. The historical reason why they were placed in an external location, is that when they were based on go-fuzz, they could not be "hidden" via the _test.go prefix. So in order to shove them away from the go-ethereum "production code", they were put aside. But now we've rewritten them to be based on golang testing, and thus can be brought back. I've left (in tests/) the ones that are not production (bls128381), require non-standard imports (secp requires btcec, bn256 requires gnark/google/cloudflare deps). This PR also adds a fuzzer for precompiled contracts, because why not. This PR utilizes a newly rewritten replacement for go-118-fuzz-build, namely gofuzz-shim, which utilises the inputs from the fuzzing engine better.
1 parent 24d4622 commit 2391fbc

24 files changed

+704
-996
lines changed

tests/fuzzers/abi/abifuzzer_test.go renamed to accounts/abi/abifuzzer_test.go

+55-58
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,31 @@ import (
2222
"strings"
2323
"testing"
2424

25-
"github.com/ethereum/go-ethereum/accounts/abi"
2625
fuzz "github.com/google/gofuzz"
2726
)
2827

2928
// TestReplicate can be used to replicate crashers from the fuzzing tests.
3029
// Just replace testString with the data in .quoted
3130
func TestReplicate(t *testing.T) {
32-
testString := "\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"
33-
data := []byte(testString)
34-
fuzzAbi(data)
31+
//t.Skip("Test only useful for reproducing issues")
32+
fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"))
33+
//fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk"))
3534
}
3635

37-
func Fuzz(f *testing.F) {
36+
// FuzzABI is the main entrypoint for fuzzing
37+
func FuzzABI(f *testing.F) {
3838
f.Fuzz(func(t *testing.T, data []byte) {
3939
fuzzAbi(data)
4040
})
4141
}
4242

4343
var (
44-
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
45-
stateMut = []string{"", "pure", "view", "payable"}
46-
stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]}
47-
pays = []string{"", "true", "false"}
48-
payables = []*string{&pays[0], &pays[1]}
49-
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
50-
varNames = append(vNames, names...)
51-
varTypes = []string{"bool", "address", "bytes", "string",
44+
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
45+
stateMut = []string{"pure", "view", "payable"}
46+
pays = []string{"true", "false"}
47+
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
48+
varNames = append(vNames, names...)
49+
varTypes = []string{"bool", "address", "bytes", "string",
5250
"uint8", "int8", "uint8", "int8", "uint16", "int16",
5351
"uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56",
5452
"uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96",
@@ -62,7 +60,7 @@ var (
6260
"bytes32", "bytes"}
6361
)
6462

65-
func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) {
63+
func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) {
6664
if out, err := abi.Unpack(method, input); err == nil {
6765
_, err := abi.Pack(method, out...)
6866
if err != nil {
@@ -78,7 +76,7 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool)
7876
return nil, false
7977
}
8078

81-
func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
79+
func packUnpack(abi ABI, method string, input *[]interface{}) bool {
8280
if packed, err := abi.Pack(method, input); err == nil {
8381
outptr := reflect.New(reflect.TypeOf(input))
8482
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
@@ -94,12 +92,12 @@ func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
9492
return false
9593
}
9694

97-
type args struct {
95+
type arg struct {
9896
name string
9997
typ string
10098
}
10199

102-
func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) {
100+
func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) {
103101
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
104102
if stateMutability != nil {
105103
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
@@ -126,56 +124,55 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab
126124
sig += "} ]"
127125
}
128126
sig += `}]`
129-
130-
return abi.JSON(strings.NewReader(sig))
127+
//fmt.Printf("sig: %s\n", sig)
128+
return JSON(strings.NewReader(sig))
131129
}
132130

133-
func fuzzAbi(input []byte) int {
134-
good := false
135-
fuzzer := fuzz.NewFromGoFuzz(input)
136-
137-
name := names[getUInt(fuzzer)%len(names)]
138-
stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)]
139-
payable := payables[getUInt(fuzzer)%len(payables)]
140-
maxLen := 5
141-
for k := 1; k < maxLen; k++ {
142-
var arg []args
143-
for i := k; i > 0; i-- {
144-
argName := varNames[i]
145-
argTyp := varTypes[getUInt(fuzzer)%len(varTypes)]
146-
if getUInt(fuzzer)%10 == 0 {
147-
argTyp += "[]"
148-
} else if getUInt(fuzzer)%10 == 0 {
149-
arrayArgs := getUInt(fuzzer)%30 + 1
150-
argTyp += fmt.Sprintf("[%d]", arrayArgs)
151-
}
152-
arg = append(arg, args{
153-
name: argName,
154-
typ: argTyp,
155-
})
131+
func fuzzAbi(input []byte) {
132+
var (
133+
fuzzer = fuzz.NewFromGoFuzz(input)
134+
name = oneOf(fuzzer, names)
135+
stateM = oneOfOrNil(fuzzer, stateMut)
136+
payable = oneOfOrNil(fuzzer, pays)
137+
arguments []arg
138+
)
139+
for i := 0; i < upTo(fuzzer, 10); i++ {
140+
argName := oneOf(fuzzer, varNames)
141+
argTyp := oneOf(fuzzer, varTypes)
142+
switch upTo(fuzzer, 10) {
143+
case 0: // 10% chance to make it a slice
144+
argTyp += "[]"
145+
case 1: // 10% chance to make it an array
146+
argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30))
147+
default:
156148
}
157-
abi, err := createABI(name, stateM, payable, arg)
158-
if err != nil {
159-
continue
160-
}
161-
structs, b := unpackPack(abi, name, input)
162-
c := packUnpack(abi, name, &structs)
163-
good = good || b || c
149+
arguments = append(arguments, arg{name: argName, typ: argTyp})
164150
}
165-
if good {
166-
return 1
151+
abi, err := createABI(name, stateM, payable, arguments)
152+
if err != nil {
153+
//fmt.Printf("err: %v\n", err)
154+
panic(err)
167155
}
168-
return 0
156+
structs, _ := unpackPack(abi, name, input)
157+
_ = packUnpack(abi, name, &structs)
169158
}
170159

171-
func getUInt(fuzzer *fuzz.Fuzzer) int {
160+
func upTo(fuzzer *fuzz.Fuzzer, max int) int {
172161
var i int
173162
fuzzer.Fuzz(&i)
174163
if i < 0 {
175-
i = -i
176-
if i < 0 {
177-
return 0
178-
}
164+
return (-1 - i) % max
165+
}
166+
return i % max
167+
}
168+
169+
func oneOf(fuzzer *fuzz.Fuzzer, options []string) string {
170+
return options[upTo(fuzzer, len(options))]
171+
}
172+
173+
func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string {
174+
if i := upTo(fuzzer, len(options)+1); i < len(options) {
175+
return &options[i]
179176
}
180-
return i
177+
return nil
181178
}

tests/fuzzers/keystore/keystore_test.go renamed to accounts/keystore/keystore_fuzzing_test.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,19 @@
1616

1717
package keystore
1818

19-
import "testing"
19+
import (
20+
"testing"
21+
)
2022

21-
func Fuzz(f *testing.F) {
22-
f.Fuzz(func(t *testing.T, data []byte) {
23-
fuzz(data)
23+
func FuzzPassword(f *testing.F) {
24+
f.Fuzz(func(t *testing.T, password string) {
25+
ks := NewKeyStore(t.TempDir(), LightScryptN, LightScryptP)
26+
a, err := ks.NewAccount(password)
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
if err := ks.Unlock(a, password); err != nil {
31+
t.Fatal(err)
32+
}
2433
})
2534
}

common/bitutil/compress_test.go

+51-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package bitutil
1818

1919
import (
2020
"bytes"
21+
"fmt"
2122
"math/rand"
2223
"testing"
2324

@@ -48,19 +49,23 @@ func TestEncodingCycle(t *testing.T) {
4849
"0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe",
4950
}
5051
for i, tt := range tests {
51-
data := hexutil.MustDecode(tt)
52-
53-
proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
54-
if err != nil {
55-
t.Errorf("test %d: failed to decompress compressed data: %v", i, err)
56-
continue
57-
}
58-
if !bytes.Equal(data, proc) {
59-
t.Errorf("test %d: compress/decompress mismatch: have %x, want %x", i, proc, data)
52+
if err := testEncodingCycle(hexutil.MustDecode(tt)); err != nil {
53+
t.Errorf("test %d: %v", i, err)
6054
}
6155
}
6256
}
6357

58+
func testEncodingCycle(data []byte) error {
59+
proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
60+
if err != nil {
61+
return fmt.Errorf("failed to decompress compressed data: %v", err)
62+
}
63+
if !bytes.Equal(data, proc) {
64+
return fmt.Errorf("compress/decompress mismatch: have %x, want %x", proc, data)
65+
}
66+
return nil
67+
}
68+
6469
// Tests that data bitset decoding and rencoding works and is bijective.
6570
func TestDecodingCycle(t *testing.T) {
6671
tests := []struct {
@@ -179,3 +184,40 @@ func benchmarkEncoding(b *testing.B, bytes int, fill float64) {
179184
bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
180185
}
181186
}
187+
188+
func FuzzEncoder(f *testing.F) {
189+
f.Fuzz(func(t *testing.T, data []byte) {
190+
if err := testEncodingCycle(data); err != nil {
191+
t.Fatal(err)
192+
}
193+
})
194+
}
195+
func FuzzDecoder(f *testing.F) {
196+
f.Fuzz(func(t *testing.T, data []byte) {
197+
fuzzDecode(data)
198+
})
199+
}
200+
201+
// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
202+
// reencoding algorithm.
203+
func fuzzDecode(data []byte) {
204+
blob, err := DecompressBytes(data, 1024)
205+
if err != nil {
206+
return
207+
}
208+
// re-compress it (it's OK if the re-compressed differs from the
209+
// original - the first input may not have been compressed at all)
210+
comp := CompressBytes(blob)
211+
if len(comp) > len(blob) {
212+
// After compression, it must be smaller or equal
213+
panic("bad compression")
214+
}
215+
// But decompressing it once again should work
216+
decomp, err := DecompressBytes(data, 1024)
217+
if err != nil {
218+
panic(err)
219+
}
220+
if !bytes.Equal(decomp, blob) {
221+
panic("content mismatch")
222+
}
223+
}

0 commit comments

Comments
 (0)