From 9712df996afbb44a7d3982dd3a424d01380f7992 Mon Sep 17 00:00:00 2001 From: Silas Davis <silas@monax.io> Date: Sat, 24 Mar 2018 18:28:20 +0000 Subject: [PATCH] Implement EIP 150 and add overflow safety Signed-off-by: Silas Davis <silas@monax.io> --- account/bytecode.go | 2 +- binary/word256.go | 1 + execution/evm/asm/opcodes.go | 13 +++- execution/evm/gas.go | 1 + execution/evm/stack.go | 23 ++++-- execution/evm/vm.go | 135 ++++++++++++++++++++++++----------- execution/transactor.go | 9 ++- rpc/tm/methods.go | 4 +- 8 files changed, 139 insertions(+), 49 deletions(-) diff --git a/account/bytecode.go b/account/bytecode.go index f0f8b9a8..01a1cdad 100644 --- a/account/bytecode.go +++ b/account/bytecode.go @@ -75,8 +75,8 @@ func (bc Bytecode) Tokens() ([]string, error) { return tokens, fmt.Errorf("did not recognise byte %#x at position %v as an OpCode:\n %s", bc[i], i, lexingPositionString(bc, i, tokens)) } - tokens = append(tokens, op.Name()) pushes := op.Pushes() + tokens = append(tokens, op.Name()) if pushes > 0 { // This is a PUSH<N> OpCode so consume N bytes from the input, render them as hex, and skip to next OpCode if i+pushes >= len(bc) { diff --git a/binary/word256.go b/binary/word256.go index 7bdbb4ec..b24f869c 100644 --- a/binary/word256.go +++ b/binary/word256.go @@ -108,6 +108,7 @@ func Uint64FromWord256(word Word256) uint64 { } func Int64FromWord256(word Word256) int64 { + buf := word.Postfix(8) return GetInt64BE(buf) } diff --git a/execution/evm/asm/opcodes.go b/execution/evm/asm/opcodes.go index f85f3b4d..0db53834 100644 --- a/execution/evm/asm/opcodes.go +++ b/execution/evm/asm/opcodes.go @@ -49,6 +49,9 @@ const ( XOR NOT BYTE + SHL + SHR + SAR SHA3 = 0x20 ) @@ -68,6 +71,8 @@ const ( GASPRICE_DEPRECATED EXTCODESIZE EXTCODECOPY + RETURNDATASIZE + RETURNDATACOPY ) const ( @@ -182,6 +187,7 @@ const ( DELEGATECALL // 0x70 range - other + STATICCALL = 0xfa REVERT = 0xfd SELFDESTRUCT = 0xff ) @@ -211,6 +217,9 @@ var opCodeNames = map[OpCode]string{ OR: "OR", XOR: "XOR", BYTE: "BYTE", + SHL: "SHL", + SHR: "SHR", + SAR: "SAR", ADDMOD: "ADDMOD", MULMOD: "MULMOD", @@ -239,6 +248,8 @@ var opCodeNames = map[OpCode]string{ GASLIMIT: "GASLIMIT", EXTCODESIZE: "EXTCODESIZE", EXTCODECOPY: "EXTCODECOPY", + RETURNDATASIZE: "RETURNDATASIZE", + RETURNDATACOPY: "RETURNDATACOPY", // 0x50 range - 'storage' and execution POP: "POP", @@ -333,7 +344,7 @@ var opCodeNames = map[OpCode]string{ RETURN: "RETURN", CALLCODE: "CALLCODE", DELEGATECALL: "DELEGATECALL", - + STATICCALL: "STATICCALL", // 0x70 range - other REVERT: "REVERT", SELFDESTRUCT: "SELFDESTRUCT", diff --git a/execution/evm/gas.go b/execution/evm/gas.go index a764a5a5..dff920c1 100644 --- a/execution/evm/gas.go +++ b/execution/evm/gas.go @@ -18,6 +18,7 @@ const ( GasSha3 uint64 = 1 GasGetAccount uint64 = 1 GasStorageUpdate uint64 = 1 + GasCreateAccount uint64 = 1 GasBaseOp uint64 = 0 // TODO: make this 1 GasStackOp uint64 = 1 diff --git a/execution/evm/stack.go b/execution/evm/stack.go index 2db80183..3e9dfd5b 100644 --- a/execution/evm/stack.go +++ b/execution/evm/stack.go @@ -103,14 +103,20 @@ func (st *Stack) PopBytes() []byte { return st.Pop().Bytes() } -func (st *Stack) Pop64() int64 { +func (st *Stack) Pop64() (int64, error) { d := st.Pop() - return Int64FromWord256(d) + if Is64BitOverflow(d) { + return 0, fmt.Errorf("int64 overflow from word: %v", d) + } + return Int64FromWord256(d), nil } -func (st *Stack) PopU64() uint64 { +func (st *Stack) PopU64() (uint64, error) { d := st.Pop() - return Uint64FromWord256(d) + if Is64BitOverflow(d) { + return 0, fmt.Errorf("uint64 overflow from word: %v", d) + } + return Uint64FromWord256(d), nil } func (st *Stack) PopBigIntSigned() *big.Int { @@ -169,3 +175,12 @@ func (st *Stack) Print(n int) { } fmt.Println("#############") } + +func Is64BitOverflow(word Word256) bool { + for i := 0; i < len(word)-8; i++ { + if word[i] != 0 { + return true + } + } + return false +} diff --git a/execution/evm/vm.go b/execution/evm/vm.go index 03a7249e..c7c6b659 100644 --- a/execution/evm/vm.go +++ b/execution/evm/vm.go @@ -19,9 +19,6 @@ import ( "errors" "fmt" - "io/ioutil" - "strings" - acm "github.com/hyperledger/burrow/account" . "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/event" @@ -205,16 +202,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address().Bytes()[:4], callee.Address(), len(callee.Code()), *gas, input) - tokens, err := acm.Bytecode(code).Tokens() - if err != nil { - return nil, err - } - tokens = append(tokens, "") - err = ioutil.WriteFile(fmt.Sprintf("tokens-%x.txt", vm.txHash[:4]), []byte(strings.Join(tokens, "\n")), 0700) - if err != nil { - return nil, err - } - + logger := vm.logger.With("tx_hash", vm.txHash) var ( pc int64 = 0 stack = NewStack(dataStackCapacity, gas, &err) @@ -325,7 +313,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" %v ** %v = %v (%X)\n", x, y, pow, res) case SIGNEXTEND: // 0x0B - back := stack.PopU64() + back, popErr := stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } if back < Word256Length-1 { stack.PushBigInt(SignExtend(back, stack.PopBigInt())) } @@ -427,7 +418,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" !%X = %X\n", x, z) case BYTE: // 0x1A - idx, val := stack.Pop64(), stack.Pop() + idx, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + val := stack.Pop() res := byte(0) if idx < 32 { res = val[idx] @@ -482,7 +477,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" => %v\n", value) case CALLDATALOAD: // 0x35 - offset := stack.Pop64() + offset, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(input, offset, 32) if !ok { return nil, firstErr(err, ErrInputOutOfBounds) @@ -497,8 +495,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case CALLDATACOPY: // 0x37 memOff := stack.PopBigInt() - inputOff := stack.Pop64() - length := stack.Pop64() + inputOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(input, inputOff, length) if !ok { return nil, firstErr(err, ErrInputOutOfBounds) @@ -517,8 +521,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case CODECOPY: // 0x39 memOff := stack.PopBigInt() - codeOff := stack.Pop64() - length := stack.Pop64() + codeOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(code, codeOff, length) if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) @@ -573,8 +583,14 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value } code := acc.Code() memOff := stack.PopBigInt() - codeOff := stack.Pop64() - length := stack.Pop64() + codeOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } data, ok := subslice(code, codeOff, length) if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) @@ -632,7 +648,12 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE8: // 0x53 - offset, val := stack.PopBigInt(), byte(stack.Pop64()&0xFF) + offset := stack.PopBigInt() + val64, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + val := byte(val64 & 0xFF) memErr := memory.Write(offset, []byte{val}) if memErr != nil { vm.Debugf(" => Memory err: %s", memErr) @@ -658,7 +679,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value vm.Debugf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 - jumpErr := vm.jump(code, stack.Pop64(), &pc) + to, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + jumpErr := vm.jump(code, to, &pc) if jumpErr != nil { vm.Debugf(" => JUMP err: %s", jumpErr) return nil, firstErr(err, jumpErr) @@ -666,7 +691,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value continue case JUMPI: // 0x57 - pos, cond := stack.Pop64(), stack.Pop() + pos, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + cond := stack.Pop() if !cond.IsZero() { jumpErr := vm.jump(code, pos, &pc) if jumpErr != nil { @@ -746,7 +775,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if !HasPermission(vm.state, callee, permission.CreateContract) { return nil, ErrPermission{"create_contract"} } - contractValue := stack.PopU64() + contractValue, popErr := stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } offset, size := stack.PopBigInt(), stack.PopBigInt() input, memErr := memory.Read(offset, size) if memErr != nil { @@ -760,8 +792,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value } // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount := DeriveNewAccount(callee, permission.GlobalAccountPermissions(vm.state), - vm.logger.With("tx_hash", vm.txHash)) + var gasErr error + if useGasNegative(gas, GasCreateAccount, &gasErr) { + return nil, firstErr(err, gasErr) + } + newAccount := DeriveNewAccount(callee, permission.GlobalAccountPermissions(vm.state), logger) vm.state.UpdateAccount(newAccount) // Run the input to get the contract code. @@ -782,7 +817,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if !HasPermission(vm.state, callee, permission.Call) { return nil, ErrPermission{"call"} } - gasLimit := stack.PopU64() + gasLimit, popErr := stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } addr := stack.Pop() // NOTE: for DELEGATECALL value is preserved from the original // caller, as such it is not stored on stack as an argument @@ -790,10 +828,19 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value // caller value is used. for CALL and CALLCODE value is stored // on stack and needs to be overwritten from the given value. if op != DELEGATECALL { - value = stack.PopU64() + value, popErr = stack.PopU64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + } + // inputs + inOffset, inSize := stack.PopBigInt(), stack.PopBigInt() + // outputs + retOffset := stack.PopBigInt() + retSize, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) } - inOffset, inSize := stack.PopBigInt(), stack.PopBigInt() // inputs - retOffset, retSize := stack.PopBigInt(), stack.Pop64() // outputs vm.Debugf(" => %X\n", addr) // Get the arguments from the memory @@ -805,11 +852,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value // Ensure that gasLimit is reasonable if *gas < gasLimit { - return nil, firstErr(err, ErrInsufficientGas) - } else { - *gas -= gasLimit - // NOTE: we will return any used gas later. + // EIP150 - the 63/64 rule - rather than error we pass this specified fraction of the total available gas + gasLimit = *gas - *gas/64 } + // NOTE: we will return any used gas later. + *gas -= gasLimit // Begin execution var ret []byte @@ -901,6 +948,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value return output, nil case REVERT: // 0xFD + return nil, fmt.Errorf("REVERT not yet fully implemented") offset, size := stack.PopBigInt(), stack.PopBigInt() output, memErr := memory.Read(offset, size) if memErr != nil { @@ -916,14 +964,19 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - // TODO if the receiver is , then make it the fee. (?) - // TODO: create account if doesn't exist (no reason not to) receiver, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) if errAcc != nil { return nil, firstErr(err, errAcc) } if receiver == nil { - return nil, firstErr(err, ErrUnknownAddress) + var gasErr error + if useGasNegative(gas, GasCreateAccount, &gasErr) { + return nil, firstErr(err, gasErr) + } + if !HasPermission(vm.state, callee, permission.CreateContract) { + return nil, firstErr(err, ErrPermission{"create_contract"}) + } + receiver = DeriveNewAccount(callee, permission.GlobalAccountPermissions(vm.state), logger) } receiver, errAdd := receiver.AddToBalance(callee.Balance()) @@ -938,9 +991,11 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value case STOP: // 0x00 return nil, nil + case STATICCALL, SHL, SHR, SAR, RETURNDATASIZE, RETURNDATACOPY: + return nil, fmt.Errorf("%s not yet implemented", op.Name()) default: vm.Debugf("(pc) %-3v Invalid opcode %X\n", pc, op) - return nil, fmt.Errorf("Invalid opcode %X", op) + return nil, fmt.Errorf("invalid opcode %X", op) } pc++ diff --git a/execution/transactor.go b/execution/transactor.go index f54cd02f..9b6d3cd0 100644 --- a/execution/transactor.go +++ b/execution/transactor.go @@ -20,6 +20,8 @@ import ( "sync" "time" + "runtime/debug" + acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/blockchain" @@ -83,7 +85,7 @@ func NewTransactor(blockchain blockchain.Blockchain, state acm.StateIterable, ev // Run a contract's code on an isolated and unpersisted state // Cannot be used to create new contracts -func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) (*Call, error) { +func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) (call *Call, err error) { if evm.RegisteredNativeContract(toAddress.Word256()) { return nil, fmt.Errorf("attempt to call native contract at address "+ "%X, but native contracts can not be called directly. Use a deployed "+ @@ -106,6 +108,11 @@ func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) ( vmach.SetPublisher(trans.eventEmitter) gas := params.GasLimit + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic from VM in simulated call: %v\n%s", r, debug.Stack()) + } + }() ret, err := vmach.Call(caller, callee, callee.Code(), data, 0, &gas) if err != nil { return nil, err diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go index 48ca428a..9ddf00ee 100644 --- a/rpc/tm/methods.go +++ b/rpc/tm/methods.go @@ -38,8 +38,8 @@ const ( CallCode = "call_code" // Names - GetName = "get_name" - ListNames = "list_names" + GetName = "get_name" + ListNames = "list_names" // Blockchain Genesis = "genesis" -- GitLab