diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index 829428e..8376274 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -966,6 +966,9 @@ class EVMAsm(object): class EVMException(Exception): pass +class EVMInstructionException(EVMException): + pass + class ConcretizeStack(EVMException): ''' Raised when a symbolic memory cell needs to be concretized. @@ -989,7 +992,7 @@ class InvalidOpcode(EVMException): pass -class Call(EVMException): +class Call(EVMInstructionException): def __init__(self, gas, to, value, data, out_offset=None, out_size=None): self.gas = gas self.to = to @@ -1008,25 +1011,25 @@ class Create(Call): class DelegateCall(Call): pass -class Stop(EVMException): +class Stop(EVMInstructionException): ''' Program reached a STOP instruction ''' pass -class Return(EVMException): +class Return(EVMInstructionException): ''' Program reached a RETURN instruction ''' def __init__(self, data): self.data = data def __reduce__(self): return (self.__class__, (self.data,) ) -class Revert(EVMException): +class Revert(EVMInstructionException): ''' Program reached a RETURN instruction ''' def __init__(self, data): self.data = data def __reduce__(self): return (self.__class__, (self.data,) ) -class SelfDestruct(EVMException): +class SelfDestruct(EVMInstructionException): ''' Program reached a RETURN instruction ''' def __init__(self, to): self.to = to @@ -1277,7 +1280,6 @@ class EVM(Eventful): last_pc = self.pc current = self.instruction - self._publish('will_execute_instruction', self.pc, current) #Consume some gas self._consume(current.fee) @@ -1300,13 +1302,15 @@ class EVM(Eventful): if isinstance(arguments[i], Constant): arguments[i] = arguments[i].value + self._publish('will_execute_instruction', self.pc, current) self._publish('will_evm_execute_instruction', current, arguments) last_pc = self.pc - #Execute + result = None + try: result = implementation(*arguments) - self._publish('did_evm_execute_instruction', current, arguments, result) + self._emit_did_execute_signals(current, arguments, result, last_pc) except ConcretizeStack as ex: for arg in reversed(arguments): self._push(arg) @@ -1318,10 +1322,17 @@ class EVM(Eventful): policy=ex.policy) except EVMException as e: self.last_exception = e - raise - self._publish( 'did_execute_instruction', last_pc, self.pc, current) - + # Technically, this is not the right place to emit these events because the + # instruction hasn't executed yet; it executes in the EVM platform class (EVMWorld). + # However, when I tried that, in the event handlers, `state.platform.current` + # ends up being None, which caused issues. So, as a pragmatic solution, we emit + # the event before technically executing the instruction. + if isinstance(e, EVMInstructionException): + self._emit_did_execute_signals(current, arguments, result, last_pc) + + raise + #Check result (push) if current.pushes > 1: assert len(result) == current.pushes @@ -1336,6 +1347,9 @@ class EVM(Eventful): #advance pc pointer self.pc += self.instruction.size + def _emit_did_execute_signals(self, current, arguments, result, last_pc): + self._publish('did_evm_execute_instruction', current, arguments, result) + self._publish('did_execute_instruction', last_pc, self.pc, current) #INSTRUCTIONS def INVALID(self): @@ -2141,6 +2155,8 @@ class EVMWorld(Platform): def execute(self): self._process_pending_transaction() try: + if self.current is None: + raise TerminateState("Trying to execute an empty transaction", testcase=False) self.current.execute() except Create as ex: self.CREATE(ex.value, ex.data) diff --git a/tests/test_eth.py b/tests/test_eth.py index 1635a0e..3f09905 100644 --- a/tests/test_eth.py +++ b/tests/test_eth.py @@ -1,7 +1,7 @@ import unittest import os -from manticore.ethereum import ManticoreEVM, IntegerOverflow +from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector THIS_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -20,3 +20,23 @@ class EthDetectors(unittest.TestCase): self.assertIn('underflow at SUB', all_findings) self.assertIn('overflow at ADD', all_findings) self.assertIn('overflow at MUL', all_findings) + +class EthTests(unittest.TestCase): + def test_emit_did_execute_end_instructions(self): + class TestDetector(Detector): + def did_evm_execute_instruction_callback(self, state, instruction, arguments, result): + if instruction.semantics in ('REVERT', 'STOP'): + with self.locked_context('insns', dict) as d: + d[instruction.semantics] = True + + mevm = ManticoreEVM() + p = TestDetector() + mevm.register_detector(p) + + filename = os.path.join(THIS_DIR, 'binaries/int_overflow.sol') + mevm.multi_tx_analysis(filename, tx_limit=1) + + self.assertIn('insns', p.context) + context = p.context['insns'] + self.assertIn('STOP', context) + self.assertIn('REVERT', context)