github.com/ethereum/go-ethereum@v1.14.3/accounts/abi/abifuzzer_test.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package abi 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "testing" 24 25 fuzz "github.com/google/gofuzz" 26 ) 27 28 // TestReplicate can be used to replicate crashers from the fuzzing tests. 29 // Just replace testString with the data in .quoted 30 func TestReplicate(t *testing.T) { 31 t.Parallel() 32 //t.Skip("Test only useful for reproducing issues") 33 fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00")) 34 //fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk")) 35 } 36 37 // FuzzABI is the main entrypoint for fuzzing 38 func FuzzABI(f *testing.F) { 39 f.Fuzz(func(t *testing.T, data []byte) { 40 fuzzAbi(data) 41 }) 42 } 43 44 var ( 45 names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} 46 stateMut = []string{"pure", "view", "payable"} 47 pays = []string{"true", "false"} 48 vNames = []string{"a", "b", "c", "d", "e", "f", "g"} 49 varNames = append(vNames, names...) 50 varTypes = []string{"bool", "address", "bytes", "string", 51 "uint8", "int8", "uint8", "int8", "uint16", "int16", 52 "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", 53 "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", 54 "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", 55 "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", 56 "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", 57 "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", 58 "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", 59 "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", 60 "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", 61 "bytes32", "bytes"} 62 ) 63 64 func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) { 65 if out, err := abi.Unpack(method, input); err == nil { 66 _, err := abi.Pack(method, out...) 67 if err != nil { 68 // We have some false positives as we can unpack these type successfully, but not pack them 69 if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" || 70 err.Error() == "abi: cannot use uint8 as type int8 as argument" { 71 return out, false 72 } 73 panic(err) 74 } 75 return out, true 76 } 77 return nil, false 78 } 79 80 func packUnpack(abi ABI, method string, input *[]interface{}) bool { 81 if packed, err := abi.Pack(method, input); err == nil { 82 outptr := reflect.New(reflect.TypeOf(input)) 83 err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) 84 if err != nil { 85 panic(err) 86 } 87 out := outptr.Elem().Interface() 88 if !reflect.DeepEqual(input, out) { 89 panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out)) 90 } 91 return true 92 } 93 return false 94 } 95 96 type arg struct { 97 name string 98 typ string 99 } 100 101 func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) { 102 sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name) 103 if stateMutability != nil { 104 sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability) 105 } 106 if payable != nil { 107 sig += fmt.Sprintf(`, "payable": %v `, *payable) 108 } 109 if len(inputs) > 0 { 110 sig += `, "inputs" : [ {` 111 for i, inp := range inputs { 112 sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) 113 if i+1 < len(inputs) { 114 sig += "," 115 } 116 } 117 sig += "} ]" 118 sig += `, "outputs" : [ {` 119 for i, inp := range inputs { 120 sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) 121 if i+1 < len(inputs) { 122 sig += "," 123 } 124 } 125 sig += "} ]" 126 } 127 sig += `}]` 128 //fmt.Printf("sig: %s\n", sig) 129 return JSON(strings.NewReader(sig)) 130 } 131 132 func fuzzAbi(input []byte) { 133 var ( 134 fuzzer = fuzz.NewFromGoFuzz(input) 135 name = oneOf(fuzzer, names) 136 stateM = oneOfOrNil(fuzzer, stateMut) 137 payable = oneOfOrNil(fuzzer, pays) 138 arguments []arg 139 ) 140 for i := 0; i < upTo(fuzzer, 10); i++ { 141 argName := oneOf(fuzzer, varNames) 142 argTyp := oneOf(fuzzer, varTypes) 143 switch upTo(fuzzer, 10) { 144 case 0: // 10% chance to make it a slice 145 argTyp += "[]" 146 case 1: // 10% chance to make it an array 147 argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30)) 148 default: 149 } 150 arguments = append(arguments, arg{name: argName, typ: argTyp}) 151 } 152 abi, err := createABI(name, stateM, payable, arguments) 153 if err != nil { 154 //fmt.Printf("err: %v\n", err) 155 panic(err) 156 } 157 structs, _ := unpackPack(abi, name, input) 158 _ = packUnpack(abi, name, &structs) 159 } 160 161 func upTo(fuzzer *fuzz.Fuzzer, max int) int { 162 var i int 163 fuzzer.Fuzz(&i) 164 if i < 0 { 165 return (-1 - i) % max 166 } 167 return i % max 168 } 169 170 func oneOf(fuzzer *fuzz.Fuzzer, options []string) string { 171 return options[upTo(fuzzer, len(options))] 172 } 173 174 func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string { 175 if i := upTo(fuzzer, len(options)+1); i < len(options) { 176 return &options[i] 177 } 178 return nil 179 }