diff --git a/mythril/laser/ethereum/instruction_data.py b/mythril/laser/ethereum/instruction_data.py index b4bdb675..8b4f6890 100644 --- a/mythril/laser/ethereum/instruction_data.py +++ b/mythril/laser/ethereum/instruction_data.py @@ -181,6 +181,9 @@ OPCODES = { "SUICIDE": {GAS: (5000, 30000), STACK: (1, 0)}, "ASSERT_FAIL": {GAS: (0, 0), STACK: (0, 0)}, "INVALID": {GAS: (0, 0), STACK: (0, 0)}, + "BEGINSUB": {GAS: (2, 2), STACK: (0, 0)}, + "JUMPSUB": {GAS: (10, 10), STACK: (1, 0)}, + "RETURNSUB": {GAS: (5, 5), STACK: (0, 0)}, } # type: Dict[str, Dict[str, Tuple[int, int]]] diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index ff29cc82..3b6679a5 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -257,7 +257,6 @@ class Instruction: raise NotImplementedError self._execute_pre_hooks(global_state) - result = instruction_mutator(global_state) self._execute_post_hooks(global_state) @@ -1618,6 +1617,42 @@ class Instruction: log.debug("Pruned unreachable states.") return states + @StateTransition() + def beginsub_(self, global_state: GlobalState) -> List[GlobalState]: + """ + This opcode depicts the start of the subroutine + """ + raise OutOfGasException("Encountered BEGINSUB") + + @StateTransition() + def jumpsub_(self, global_state: GlobalState) -> List[GlobalState]: + """ + Jump to the subroutine + """ + disassembly = global_state.environment.code + try: + location = util.get_concrete_int(global_state.mstate.stack.pop()) + except TypeError: + raise VmException("Encountered symbolic JUMPSUB location") + index = util.get_instruction_index(disassembly.instruction_list, location) + instr = disassembly.instruction_list[index] + + if instr["opcode"] != "BEGINSUB": + raise VmException( + "Encountered invalid JUMPSUB location :{}".format(instr["address"]) + ) + global_state.mstate.subroutine_stack.append(global_state.mstate.pc + 1) + global_state.mstate.pc = location + return [global_state] + + @StateTransition(increment_pc=False) + def returnsub_(self, global_state: GlobalState) -> List[GlobalState]: + """ + Returns control to the caller of the subroutine + """ + global_state.mstate.pc = global_state.mstate.subroutine_stack.pop() + return [global_state] + @StateTransition() def pc_(self, global_state: GlobalState) -> List[GlobalState]: """ @@ -1629,7 +1664,7 @@ class Instruction: program_counter = global_state.environment.code.instruction_list[index][ "address" ] - global_state.mstate.stack.append(program_counter) + global_state.mstate.stack.append(symbol_factory.BitVecVal(program_counter, 256)) return [global_state] diff --git a/mythril/laser/ethereum/state/machine_state.py b/mythril/laser/ethereum/state/machine_state.py index 4fc734ea..ead279b8 100644 --- a/mythril/laser/ethereum/state/machine_state.py +++ b/mythril/laser/ethereum/state/machine_state.py @@ -89,6 +89,7 @@ class MachineState: gas_limit: int, pc=0, stack=None, + subroutine_stack=None, memory: Optional[Memory] = None, constraints=None, depth=0, @@ -110,6 +111,7 @@ class MachineState: """ self._pc = pc self.stack = MachineStack(stack) + self.subroutine_stack = MachineStack(subroutine_stack) self.memory = memory or Memory() self.gas_limit = gas_limit self.min_gas_used = min_gas_used # lower gas usage bound @@ -216,6 +218,7 @@ class MachineState: memory=copy(self.memory), depth=self.depth, prev_pc=self.prev_pc, + subroutine_stack=copy(self.subroutine_stack), ) def __str__(self): @@ -255,6 +258,7 @@ class MachineState: return dict( pc=self._pc, stack=self.stack, + subroutine_stack=self.subroutine_stack, memory=self.memory, memsize=self.memory_size, gas=self.gas_limit, diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 96fc95a2..352e5a6a 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -313,7 +313,6 @@ class LaserEVM: hook(global_state) instructions = global_state.environment.code.instruction_list - try: op_code = instructions[global_state.mstate.pc]["opcode"] except IndexError: diff --git a/mythril/support/opcodes.py b/mythril/support/opcodes.py index 70501a91..3ba79cc7 100644 --- a/mythril/support/opcodes.py +++ b/mythril/support/opcodes.py @@ -65,6 +65,9 @@ opcodes = { 0x59: ("MSIZE", 0, 1, 2), 0x5A: ("GAS", 0, 1, 2), 0x5B: ("JUMPDEST", 0, 0, 1), + 0x5C: ("BEGINSUB", 0, 0, 2), + 0x5D: ("RETURNSUB", 0, 0, 5), + 0x5E: ("JUMPSUB", 1, 0, 10), 0xA0: ("LOG0", 2, 0, 375), 0xA1: ("LOG1", 3, 0, 750), 0xA2: ("LOG2", 4, 0, 1125), diff --git a/tests/instructions/berlin_fork_opcodes_test.py b/tests/instructions/berlin_fork_opcodes_test.py new file mode 100644 index 00000000..b647d100 --- /dev/null +++ b/tests/instructions/berlin_fork_opcodes_test.py @@ -0,0 +1,89 @@ +import pytest + +from mythril.laser.ethereum.evm_exceptions import VmException, OutOfGasException +from mythril.disassembler.disassembly import Disassembly +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.instructions import Instruction +from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction +from mythril.laser.smt import symbol_factory, simplify + + +def get_state(): + world_state = WorldState() + account = world_state.create_account(balance=10, address=101) + account.code = Disassembly("0x60045e005c5d") + environment = Environment(account, None, None, None, None, None) + state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000)) + state.transaction_stack.append( + (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) + ) + return state + + +BVV = symbol_factory.BitVecVal +BV = symbol_factory.BitVecSym + +test_data = (([BVV(5, 256)], [], 2, -1, ()),) + + +def test_jumpsub_success(): + # Arrange + state = get_state() + state.mstate.pc = 2 + state.mstate.stack = [BVV(4, 256)] + state.mstate.subroutine_stack = [] + instruction = Instruction("jumpsub", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert new_state.mstate.pc == 5 + assert new_state.mstate.stack == [] + assert new_state.mstate.subroutine_stack == [3] + + +def test_jumpsub_fail(): + # Arrange + state = get_state() + state.mstate.pc = 2 + state.mstate.stack = [BVV(5, 256)] + state.mstate.subroutine_stack = [] + instruction = Instruction("jumpsub", dynamic_loader=None) + + # Act + Assert + with pytest.raises(VmException): + instruction.evaluate(state)[0] + + +def test_beginsub(): + # Arrange + state = get_state() + state.mstate.pc = 3 + state.mstate.stack = [] + state.mstate.subroutine_stack = [] + instruction = Instruction("beginsub", dynamic_loader=None) + + # Act + Assert + with pytest.raises(OutOfGasException): + instruction.evaluate(state)[0] + + +def test_returnsub(): + # Arrange + state = get_state() + state.mstate.pc = 5 + state.mstate.stack = [] + state.mstate.subroutine_stack = [3] + instruction = Instruction("returnsub", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert new_state.mstate.pc == 3