Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests/fuzzers/abi: add fuzzer for fuzzing package accounts/abi #21217

Merged
merged 12 commits into from
Sep 16, 2020
Merged
4 changes: 2 additions & 2 deletions accounts/abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (

const jsondata = `
[
{ "type" : "function", "name" : "", "stateMutability" : "view" },
{ "type" : "function", "name" : ""},
{ "type" : "function", "name" : "balance", "stateMutability" : "view" },
{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
{ "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
Expand Down Expand Up @@ -88,7 +88,7 @@ var (
)

var methods = map[string]Method{
"": NewMethod("", "", Function, "view", false, false, nil, nil),
"": NewMethod("", "", Function, "", false, false, nil, nil),
"balance": NewMethod("balance", "balance", Function, "view", false, false, nil, nil),
"send": NewMethod("send", "send", Function, "", false, false, []Argument{{"amount", Uint256, false}}, nil),
"test": NewMethod("test", "test", Function, "", false, false, []Argument{{"number", Uint32, false}}, nil),
Expand Down
2 changes: 1 addition & 1 deletion accounts/abi/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func sliceTypeCheck(t Type, val reflect.Value) error {
}
}

if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.GetType().Kind() {
if val.Type().Elem().Kind() != t.Elem.GetType().Kind() {
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type())
}
return nil
Expand Down
23 changes: 14 additions & 9 deletions accounts/abi/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package abi

import (
"errors"
"fmt"
"math/big"
"reflect"

Expand All @@ -33,35 +35,38 @@ func packBytesSlice(bytes []byte, l int) []byte {

// packElement packs the given reflect value according to the abi specification in
// t.
func packElement(t Type, reflectValue reflect.Value) []byte {
func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
switch t.T {
case IntTy, UintTy:
return packNum(reflectValue)
return packNum(reflectValue), nil
case StringTy:
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len())
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil
case AddressTy:
if reflectValue.Kind() == reflect.Array {
reflectValue = mustArrayToByteSlice(reflectValue)
}

return common.LeftPadBytes(reflectValue.Bytes(), 32)
return common.LeftPadBytes(reflectValue.Bytes(), 32), nil
case BoolTy:
if reflectValue.Bool() {
return math.PaddedBigBytes(common.Big1, 32)
return math.PaddedBigBytes(common.Big1, 32), nil
}
return math.PaddedBigBytes(common.Big0, 32)
return math.PaddedBigBytes(common.Big0, 32), nil
case BytesTy:
if reflectValue.Kind() == reflect.Array {
reflectValue = mustArrayToByteSlice(reflectValue)
}
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len())
if reflectValue.Type() != reflect.TypeOf([]byte{}) {
return []byte{}, errors.New("Bytes type is neither slice nor array")
}
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
case FixedBytesTy, FunctionTy:
if reflectValue.Kind() == reflect.Array {
reflectValue = mustArrayToByteSlice(reflectValue)
}
return common.RightPadBytes(reflectValue.Bytes(), 32)
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
default:
panic("abi: fatal error")
return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T)
}
}

Expand Down
2 changes: 1 addition & 1 deletion accounts/abi/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
return append(ret, tail...), nil

default:
return packElement(t, v), nil
return packElement(t, v)
}
}

Expand Down
5 changes: 4 additions & 1 deletion accounts/abi/unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,10 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
return forEachUnpack(t, output[begin:], 0, length)
case ArrayTy:
if isDynamicType(*t.Elem) {
offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]))
offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:])
if offset > uint64(len(output)) {
return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output))
}
return forEachUnpack(t, output[offset:], 0, t.Size)
}
return forEachUnpack(t, output[index:], 0, t.Size)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 // indirect
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c
github.com/fatih/color v1.3.0
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc
Expand All @@ -27,6 +28,7 @@ require (
github.com/go-stack/stack v1.8.0
github.com/golang/protobuf v1.4.2
github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa
github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989
github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277
github.com/hashicorp/golang-lru v0.5.4
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ=
github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM=
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs=
Expand Down Expand Up @@ -99,6 +101,10 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54=
github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM=
Expand Down
186 changes: 186 additions & 0 deletions tests/fuzzers/abi/abifuzzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package abi

import (
"bytes"
"fmt"
"math/rand"
"reflect"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/crypto"
fuzz "github.com/google/gofuzz"
)

func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byte) bool {
outptr := reflect.New(reflect.TypeOf(inputType))
if err := abi.Unpack(outptr.Interface(), method, input); err == nil {
output, err := abi.Pack(method, input)
if err != nil {
// We have some false positives as we can unpack these type successfully, but not pack them
if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" ||
err.Error() == "abi: cannot use uint8 as type int8 as argument" {
return false
}
panic(err)
}
if !bytes.Equal(input, output[4:]) {
panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, output[4:]))
}
return true
}
return false
}

func packUnpack(abi abi.ABI, method string, input []interface{}) bool {
if packed, err := abi.Pack(method, input); err == nil {
outptr := reflect.New(reflect.TypeOf(input))
err := abi.Unpack(outptr.Interface(), method, packed)
if err != nil {
panic(err)
}
out := outptr.Elem().Interface()
if !reflect.DeepEqual(input, out) {
panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out))
}
return true
}
return false
}

type args struct {
name string
typ string
}

func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) {
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
if stateMutability != nil {
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
}
if payable != nil {
sig += fmt.Sprintf(`, "payable": %v `, *payable)
}
if len(inputs) > 0 {
sig += `, "inputs" : [ {`
for i, inp := range inputs {
sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ)
if i+1 < len(inputs) {
sig += ","
}
}
sig += "} ]"
sig += `, "outputs" : [ {`
for i, inp := range inputs {
sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ)
if i+1 < len(inputs) {
sig += ","
}
}
sig += "} ]"
}
sig += `}]`

return abi.JSON(strings.NewReader(sig))
}

func fillStruct(structs []interface{}, data []byte) {
if structs != nil && len(data) != 0 {
fuzz.NewFromGoFuzz(data).Fuzz(&structs)
}
}

func createStructs(args []args) []interface{} {
structs := make([]interface{}, len(args))
for i, arg := range args {
t, err := abi.NewType(arg.typ, "", nil)
if err != nil {
panic(err)
}
structs[i] = reflect.New(t.GetType()).Elem()
}
return structs
}

func runFuzzer(input []byte) int {
good := false

names := []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
stateMut := []string{"", "pure", "view", "payable"}
stateMutabilites := []*string{nil, &stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]}
pays := []string{"true", "false"}
payables := []*string{nil, &pays[0], &pays[1]}
varNames := []string{"a", "b", "c", "d", "e", "f", "g"}
varNames = append(varNames, names...)
varTypes := []string{"bool", "address", "bytes", "string",
"uint8", "int8", "uint8", "int8", "uint16", "int16",
"uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56",
"uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96",
"uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136",
"uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176",
"uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216",
"uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256",
"bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11",
"bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21",
"bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31",
"bytes32", "bytes"}
rnd := rand.New(rand.NewSource(123456))
if len(input) > 0 {
kec := crypto.Keccak256(input)
rnd = rand.New(rand.NewSource(int64(kec[0])))
}
name := names[rnd.Intn(len(names))]
stateM := stateMutabilites[rnd.Intn(len(stateMutabilites))]
payable := payables[rnd.Intn(len(payables))]
maxLen := 5
for k := 1; k < maxLen; k++ {
var arg []args
for i := k; i > 0; i-- {
argName := varNames[i]
argTyp := varTypes[rnd.Int31n(int32(len(varTypes)))]
if rnd.Int31n(10) == 0 {
argTyp += "[]"
} else if rnd.Int31n(10) == 0 {
arrayArgs := rnd.Int31n(30) + 1
argTyp += fmt.Sprintf("[%d]", arrayArgs)
}
arg = append(arg, args{
name: argName,
typ: argTyp,
})
}
abi, err := createABI(name, stateM, payable, arg)
if err != nil {
continue
}
structs := createStructs(arg)
b := unpackPack(abi, name, structs, input)
fillStruct(structs, input)
c := packUnpack(abi, name, structs)
good = good || b || c
}
if good {
return 1
}
return 0
}

func Fuzz(input []byte) int {
return runFuzzer(input)
}
50 changes: 50 additions & 0 deletions tests/fuzzers/abi/abifuzzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package abi

import (
"testing"
)

// TestReplicate can be used to replicate crashers from the fuzzing tests.
// Just replace testString with the data in .quoted
func TestReplicate(t *testing.T) {
testString := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00000000000" +
"00000000000000000000" +
"00000000000000000000" +
"00000001"

data := []byte(testString)
runFuzzer(data)
}

// TestGenerateCorpus can be used to add corpus for the fuzzer.
// Just replace corpusHex with the hexEncoded output you want to add to the fuzzer.
func TestGenerateCorpus(t *testing.T) {
/*
corpusHex := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
data := common.FromHex(corpusHex)
checksum := sha1.Sum(data)
outf := fmt.Sprintf("corpus/%x", checksum)
if err := ioutil.WriteFile(outf, data, 0777); err != nil {
panic(err)
}
*/
}