From 0d1d069b0e313a7db8a9b5ec92ad90b229f517d0 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 20 Sep 2024 14:57:38 +0300 Subject: [PATCH] Add boundary checks for ABI encoding values of type UInt & Int --- fvm/evm/impl/abi.go | 22 ++- fvm/evm/stdlib/contract_test.go | 243 ++++++++++++++++++++++++++------ 2 files changed, 220 insertions(+), 45 deletions(-) diff --git a/fvm/evm/impl/abi.go b/fvm/evm/impl/abi.go index 5b88da53845..871d523abef 100644 --- a/fvm/evm/impl/abi.go +++ b/fvm/evm/impl/abi.go @@ -9,6 +9,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" gethABI "github.com/onflow/go-ethereum/accounts/abi" gethCommon "github.com/onflow/go-ethereum/common" @@ -21,7 +22,8 @@ const abiEncodingByteSize = 32 // abiEncodingError type abiEncodingError struct { - Type interpreter.StaticType + Type interpreter.StaticType + Message string } var _ errors.UserError = abiEncodingError{} @@ -38,6 +40,12 @@ func (e abiEncodingError) Error() string { b.WriteString(ty.String()) } + message := e.Message + if message != "" { + b.WriteString(": ") + b.WriteString(message) + } + return b.String() } @@ -430,6 +438,12 @@ func encodeABI( case interpreter.UIntValue: if staticType == interpreter.PrimitiveStaticTypeUInt { + if value.BigInt.Cmp(sema.UInt256TypeMaxIntBig) > 0 || value.BigInt.Cmp(sema.UInt256TypeMinIntBig) < 0 { + return nil, gethABI.Type{}, abiEncodingError{ + Type: value.StaticType(inter), + Message: "value outside the boundaries of uint256", + } + } return value.BigInt, gethTypeUint, nil } @@ -465,6 +479,12 @@ func encodeABI( case interpreter.IntValue: if staticType == interpreter.PrimitiveStaticTypeInt { + if value.BigInt.Cmp(sema.Int256TypeMaxIntBig) > 0 || value.BigInt.Cmp(sema.Int256TypeMinIntBig) < 0 { + return nil, gethABI.Type{}, abiEncodingError{ + Type: value.StaticType(inter), + Message: "value outside the boundaries of int256", + } + } return value.BigInt, gethTypeInt, nil } diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 7156cc44be3..f64963f59db 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -994,38 +994,11 @@ func TestEVMEncodeDecodeABIRoundtripForUintIntTypes(t *testing.T) { t.Parallel() handler := &testContractHandler{} - contractsAddress := flow.BytesToAddress([]byte{0x1}) - transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) - rt := runtime.NewInterpreterRuntime(runtime.Config{}) - script := []byte(` - import EVM from 0x1 - - access(all) - fun main(): Bool { - // Check UInt*/Int* encode/decode - let amount: UInt256 = 18446744073709551615 - let minBalance: Int256 = -18446744073709551615 - let data = EVM.encodeABIWithSignature( - "withdraw(uint,int)", - [UInt(amount), Int(minBalance)] - ) - let values = EVM.decodeABIWithSignature( - "withdraw(uint,int)", - types: [Type(), Type()], - data: data - ) - assert((values[0] as! UInt) == UInt(amount)) - assert((values[1] as! Int) == Int(minBalance)) - - return true - } - `) - accountCodes := map[common.Location][]byte{} var events []cadence.Event @@ -1073,25 +1046,207 @@ func TestEVMEncodeDecodeABIRoundtripForUintIntTypes(t *testing.T) { nextTransactionLocation, ) - // Run script + t.Run("with values between the boundaries", func(t *testing.T) { - result, err := rt.ExecuteScript( - runtime.Script{ - Source: script, - Arguments: [][]byte{}, - }, - runtime.Context{ - Interface: runtimeInterface, - Environment: scriptEnvironment, - Location: nextScriptLocation(), - }, - ) - require.NoError(t, err) + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + // Check UInt/Int encode/decode + let amount: UInt256 = 18446744073709551615 + let minBalance: Int256 = -18446744073709551615 + let data = EVM.encodeABIWithSignature( + "withdraw(uint,int)", + [UInt(amount), Int(minBalance)] + ) + let values = EVM.decodeABIWithSignature( + "withdraw(uint,int)", + types: [Type(), Type()], + data: data + ) + assert((values[0] as! UInt) == UInt(amount)) + assert((values[1] as! Int) == Int(minBalance)) - assert.Equal(t, - cadence.Bool(true), - result, - ) + return true + } + `) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, cadence.Bool(true), result) + }) + + t.Run("with values at the boundaries", func(t *testing.T) { + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + // Check UInt*/Int* encode/decode + let data = EVM.encodeABIWithSignature( + "withdraw(uint,int,uint,int)", + [UInt(UInt256.max), Int(Int256.max),UInt(UInt256.min), Int(Int256.min)] + ) + let values = EVM.decodeABIWithSignature( + "withdraw(uint,int,uint,int)", + types: [Type(), Type(),Type(), Type()], + data: data + ) + assert((values[0] as! UInt) == UInt(UInt256.max)) + assert((values[1] as! Int) == Int(Int256.max)) + assert((values[2] as! UInt) == UInt(UInt256.min)) + assert((values[3] as! Int) == Int(Int256.min)) + + return true + } + `) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + assert.Equal(t, cadence.Bool(true), result) + }) + + t.Run("with UInt values outside the boundaries", func(t *testing.T) { + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABIWithSignature( + "withdraw(uint)", + [UInt(UInt256.max)+10] + ) + + return true + } + `) + + // Run script + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.Error(t, err) + + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type UInt: value outside the boundaries of uint256", + ) + }) + + t.Run("with Int values outside the max boundary", func(t *testing.T) { + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABIWithSignature( + "withdraw(int)", + [Int(Int256.max)+10] + ) + + return true + } + `) + + // Run script + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.Error(t, err) + + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type Int: value outside the boundaries of int256", + ) + }) + + t.Run("with Int values outside the min boundary", func(t *testing.T) { + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): Bool { + let data = EVM.encodeABIWithSignature( + "withdraw(int)", + [Int(Int256.min)-10] + ) + + return true + } + `) + + // Run script + + _, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + Arguments: [][]byte{}, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: scriptEnvironment, + Location: nextScriptLocation(), + }, + ) + require.Error(t, err) + + assert.ErrorContains( + t, + err, + "failed to ABI encode value of type Int: value outside the boundaries of int256", + ) + }) } func TestEVMEncodeDecodeABIRoundtrip(t *testing.T) {