forked from cerc-io/plugeth
Merge pull request #15770 from holiman/abi_nostruct
accounts/abi: add another unpack interface
This commit is contained in:
commit
f54506ccf8
@ -136,11 +136,11 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// MethodById looks up a method by the 4-byte id
|
// MethodById looks up a method by the 4-byte id
|
||||||
// returns nil if none found
|
// returns nil if none found
|
||||||
func (abi *ABI) MethodById(sigdata []byte) *Method {
|
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
||||||
for _, method := range abi.Methods {
|
for _, method := range abi.Methods {
|
||||||
if bytes.Equal(method.Id(), sigdata[:4]) {
|
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||||
return &method
|
return &method, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
|
||||||
}
|
}
|
||||||
|
@ -689,7 +689,11 @@ func TestABI_MethodById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for name, m := range abi.Methods {
|
for name, m := range abi.Methods {
|
||||||
a := fmt.Sprintf("%v", m)
|
a := fmt.Sprintf("%v", m)
|
||||||
b := fmt.Sprintf("%v", abi.MethodById(m.Id()))
|
m2, err := abi.MethodById(m.Id())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to look up ABI method: %v", err)
|
||||||
|
}
|
||||||
|
b := fmt.Sprintf("%v", m2)
|
||||||
if a != b {
|
if a != b {
|
||||||
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,17 @@ func (arguments Arguments) LengthNonIndexed() int {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NonIndexed returns the arguments with indexed arguments filtered out
|
||||||
|
func (arguments Arguments) NonIndexed() Arguments {
|
||||||
|
var ret []Argument
|
||||||
|
for _, arg := range arguments {
|
||||||
|
if !arg.Indexed {
|
||||||
|
ret = append(ret, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
||||||
func (arguments Arguments) isTuple() bool {
|
func (arguments Arguments) isTuple() bool {
|
||||||
return len(arguments) > 1
|
return len(arguments) > 1
|
||||||
@ -74,21 +85,25 @@ func (arguments Arguments) isTuple() bool {
|
|||||||
|
|
||||||
// Unpack performs the operation hexdata -> Go format
|
// Unpack performs the operation hexdata -> Go format
|
||||||
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
||||||
if arguments.isTuple() {
|
|
||||||
return arguments.unpackTuple(v, data)
|
|
||||||
}
|
|
||||||
return arguments.unpackAtomic(v, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
|
||||||
// make sure the passed value is arguments pointer
|
// make sure the passed value is arguments pointer
|
||||||
valueOf := reflect.ValueOf(v)
|
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
}
|
}
|
||||||
|
marshalledValues, err := arguments.UnpackValues(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if arguments.isTuple() {
|
||||||
|
return arguments.unpackTuple(v, marshalledValues)
|
||||||
|
}
|
||||||
|
return arguments.unpackAtomic(v, marshalledValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
value = valueOf.Elem()
|
value = reflect.ValueOf(v).Elem()
|
||||||
typ = value.Type()
|
typ = value.Type()
|
||||||
kind = value.Kind()
|
kind = value.Kind()
|
||||||
)
|
)
|
||||||
@ -110,30 +125,9 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
|||||||
exists[field] = true
|
exists[field] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// `i` counts the nonindexed arguments.
|
for i, arg := range arguments.NonIndexed() {
|
||||||
// `j` counts the number of complex types.
|
|
||||||
// both `i` and `j` are used to to correctly compute `data` offset.
|
|
||||||
|
|
||||||
i, j := -1, 0
|
reflectValue := reflect.ValueOf(marshalledValues[i])
|
||||||
for _, arg := range arguments {
|
|
||||||
|
|
||||||
if arg.Indexed {
|
|
||||||
// can't read, continue
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if arg.Type.T == ArrayTy {
|
|
||||||
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
|
|
||||||
// we need to decrement 'j' because 'i' was incremented
|
|
||||||
j += arg.Type.Size - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
@ -166,34 +160,52 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
||||||
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
|
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error {
|
||||||
// make sure the passed value is arguments pointer
|
if len(marshalledValues) != 1 {
|
||||||
valueOf := reflect.ValueOf(v)
|
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
}
|
||||||
arg := arguments[0]
|
elem := reflect.ValueOf(v).Elem()
|
||||||
if arg.Indexed {
|
reflectValue := reflect.ValueOf(marshalledValues[0])
|
||||||
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
return set(elem, reflectValue, arguments.NonIndexed()[0])
|
||||||
}
|
|
||||||
|
|
||||||
value := valueOf.Elem()
|
|
||||||
|
|
||||||
marshalledValue, err := toGoType(0, arg.Type, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return set(value, reflect.ValueOf(marshalledValue), arg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack performs the operation Go format -> Hexdata
|
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
|
||||||
|
// without supplying a struct to unpack into. Instead, this method returns a list containing the
|
||||||
|
// values. An atomic argument will be a list with one element.
|
||||||
|
func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
|
||||||
|
retval := make([]interface{}, 0, arguments.LengthNonIndexed())
|
||||||
|
virtualArgs := 0
|
||||||
|
for index, arg := range arguments.NonIndexed() {
|
||||||
|
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
|
||||||
|
if arg.Type.T == ArrayTy {
|
||||||
|
// If we have a static array, like [3]uint256, these are coded as
|
||||||
|
// just like uint256,uint256,uint256.
|
||||||
|
// This means that we need to add two 'virtual' arguments when
|
||||||
|
// we count the index from now on
|
||||||
|
|
||||||
|
virtualArgs += arg.Type.Size - 1
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
retval = append(retval, marshalledValue)
|
||||||
|
}
|
||||||
|
return retval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackValues performs the operation Go format -> Hexdata
|
||||||
|
// It is the semantic opposite of UnpackValues
|
||||||
|
func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) {
|
||||||
|
return arguments.Pack(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack performs the operation Go format -> Hexdata
|
||||||
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
// Make sure arguments match up and pack them
|
// Make sure arguments match up and pack them
|
||||||
abiArgs := arguments
|
abiArgs := arguments
|
||||||
if len(args) != len(abiArgs) {
|
if len(args) != len(abiArgs) {
|
||||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// variable input is the output appended at the end of packed
|
// variable input is the output appended at the end of packed
|
||||||
// output. This is used for strings and bytes types input.
|
// output. This is used for strings and bytes types input.
|
||||||
var variableInput []byte
|
var variableInput []byte
|
||||||
@ -207,7 +219,6 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
|||||||
inputOffset += 32
|
inputOffset += 32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for i, a := range args {
|
for i, a := range args {
|
||||||
input := abiArgs[i]
|
input := abiArgs[i]
|
||||||
@ -216,7 +227,6 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for a slice type (string, bytes, slice)
|
// check for a slice type (string, bytes, slice)
|
||||||
if input.Type.requiresLengthPrefix() {
|
if input.Type.requiresLengthPrefix() {
|
||||||
// calculate the offset
|
// calculate the offset
|
||||||
|
@ -95,6 +95,9 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
|||||||
|
|
||||||
// iteratively unpack elements
|
// iteratively unpack elements
|
||||||
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
|
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
|
||||||
|
if size < 0 {
|
||||||
|
return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size)
|
||||||
|
}
|
||||||
if start+32*size > len(output) {
|
if start+32*size > len(output) {
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
|
return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
|
||||||
}
|
}
|
||||||
@ -181,16 +184,32 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
|||||||
|
|
||||||
// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type.
|
// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type.
|
||||||
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
|
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
|
||||||
offset := int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32])
|
||||||
if offset+32 > len(output) {
|
bigOffsetEnd.Add(bigOffsetEnd, common.Big32)
|
||||||
return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32)
|
outputLength := big.NewInt(int64(len(output)))
|
||||||
}
|
|
||||||
length = int(binary.BigEndian.Uint64(output[offset+24 : offset+32]))
|
|
||||||
if offset+32+length > len(output) {
|
|
||||||
return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+length)
|
|
||||||
}
|
|
||||||
start = offset + 32
|
|
||||||
|
|
||||||
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
|
if bigOffsetEnd.Cmp(outputLength) > 0 {
|
||||||
|
return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bigOffsetEnd.BitLen() > 63 {
|
||||||
|
return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetEnd := int(bigOffsetEnd.Uint64())
|
||||||
|
lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd])
|
||||||
|
|
||||||
|
totalSize := big.NewInt(0)
|
||||||
|
totalSize.Add(totalSize, bigOffsetEnd)
|
||||||
|
totalSize.Add(totalSize, lengthBig)
|
||||||
|
if totalSize.BitLen() > 63 {
|
||||||
|
return 0, 0, fmt.Errorf("abi length larger than int64: %v", totalSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalSize.Cmp(outputLength) > 0 {
|
||||||
|
return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize)
|
||||||
|
}
|
||||||
|
start = int(bigOffsetEnd.Uint64())
|
||||||
|
length = int(lengthBig.Uint64())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ var unpackTests = []unpackTest{
|
|||||||
{
|
{
|
||||||
def: `[{"type": "bytes32"}]`,
|
def: `[{"type": "bytes32"}]`,
|
||||||
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
want: common.HexToHash("0100000000000000000000000000000000000000000000000000000000000000"),
|
want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "function"}]`,
|
def: `[{"type": "function"}]`,
|
||||||
@ -683,3 +683,73 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
t.Fatal("expected error:", err)
|
t.Fatal("expected error:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOOMMaliciousInput(t *testing.T) {
|
||||||
|
oomTests := []unpackTest{
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
{ // Length larger than 64 bits
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||||
|
"00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000002" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
{ // Offset very large (over 64 bits)
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000020" + // offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
{ // Offset very large (below 64 bits)
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000007ffffffffff00020" + // offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
{ // Offset negative (as 64 bit)
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "000000000000000000000000000000000000000000000000f000000000000020" + // offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
|
||||||
|
{ // Negative length
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||||
|
"000000000000000000000000000000000000000000000000f000000000000002" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
{ // Very large length
|
||||||
|
def: `[{"type": "uint8[]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||||
|
"0000000000000000000000000000000000000000000000007fffffffff000002" + // num elems
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range oomTests {
|
||||||
|
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
||||||
|
abi, err := JSON(strings.NewReader(def))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ABI definition %s: %v", def, err)
|
||||||
|
}
|
||||||
|
encb, err := hex.DecodeString(test.enc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid hex: %s" + test.enc)
|
||||||
|
}
|
||||||
|
_, err = abi.Methods["method"].Outputs.UnpackValues(encb)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error on malicious input, test %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user