diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index 81cb576..5d4944d 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -1,6 +1,8 @@ ''' Symbolic EVM implementation based on the yellow paper: http://gavwood.com/paper.pdf ''' import random import copy +import inspect +from functools import wraps from ..utils.helpers import issymbolic, memoized from ..platforms.platform import * from ..core.smtlib import solver, TooManySolutions, Expression, Bool, BitVec, Array, Operators, Constant, BitVecConstant, ConstraintSet, \ @@ -1067,6 +1069,43 @@ class Sha3(EVMException): return (self.__class__, (self.data, )) +def concretized_args(**policies): + """ + Make sure an EVM instruction has all of its arguments concretized according to + provided policies. + + Example decoration: + + @concretized_args(size='ONE', address='') + def LOG(self, address, size, *topics): + ... + + The above will make sure that the |size| parameter to LOG is Concretized when symbolic + according to the 'ONE' policy and concretize |address| with the default policy. + + :param policies: A kwargs list of argument names and their respective policies. + Provide None or '' as policy to use default. + :return: A function decorator + """ + def concretizer(func): + @wraps(func) + def wrapper(*args, **kwargs): + spec = inspect.getargspec(func) + for arg, policy in policies.items(): + assert arg in spec.args, "Concretizer argument not found in wrapped function." + # index is 0-indexed, but ConcretizeStack is 1-indexed. However, this is correct + # since implementation method is always a bound method (self is param 0) + index = spec.args.index(arg) + if issymbolic(args[index]): + if policy: + raise ConcretizeStack(index, policy=policy) + else: + raise ConcretizeStack(index) + return func(*args, **kwargs) + return wrapper + return concretizer + + class EVM(Eventful): '''Machine State. The machine state is defined as the tuple (g, pc, m, i, s) which are the gas available, the @@ -1614,10 +1653,9 @@ class EVM(Eventful): '''Get size of code running in current environment''' return len(self.bytecode) + @concretized_args(size='') def CODECOPY(self, mem_offset, code_offset, size): '''Copy code running in current environment to memory''' - if issymbolic(size): - raise ConcretizeStack(3) GCOPY = 3 # cost to copy one 32 byte word self._consume(GCOPY * ceil32(size) // 32) @@ -1775,11 +1813,9 @@ class EVM(Eventful): ########################################################################## # Logging Operations + @concretized_args(size='ONE') def LOG(self, address, size, *topics): - if issymbolic(size): - raise ConcretizeStack(2, policy='ONE') - memlog = self.read_buffer(address, size) self.logs.append(EVMLog(self.address, memlog, topics)) @@ -1813,12 +1849,9 @@ class EVM(Eventful): code = self.read_buffer(offset, size) raise Create(value, code) + @concretized_args(in_offset='SAMPLED', in_size='SAMPLED') def CALL(self, gas, to, value, in_offset, in_size, out_offset, out_size): '''Message-call into an account''' - if issymbolic(in_offset): - raise ConcretizeStack(4, policy='SAMPLED') - if issymbolic(in_size): - raise ConcretizeStack(5, policy='SAMPLED') data = self.read_buffer(in_offset, in_size) raise Call(gas, to, value, data, out_offset, out_size) diff --git a/tests/test_eth.py b/tests/test_eth.py index 23c3b3e..59ff8f5 100644 --- a/tests/test_eth.py +++ b/tests/test_eth.py @@ -2,9 +2,11 @@ import unittest import os from manticore.core.smtlib import ConstraintSet, operators +from manticore.core.smtlib.expression import BitVec from manticore.core.state import State from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector -from manticore.platforms.evm import EVMWorld +from manticore.platforms.evm import EVMWorld, ConcretizeStack, concretized_args + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -122,3 +124,47 @@ class EthTests(unittest.TestCase): x = mevm.create_account(balance=0) contract_account = mevm.solidity_create_contract(source_code, contract_name="C", owner=owner, args=[x]) + + +class EthHelpers(unittest.TestCase): + def setUp(self): + self.bv = BitVec(256) + + def test_concretizer(self): + policy = 'SOME_NONSTANDARD_POLICY' + + @concretized_args(a=policy) + def inner_func(self, a, b): + return a, b + + with self.assertRaises(ConcretizeStack) as cm: + inner_func(None, self.bv, 34) + + self.assertEquals(cm.exception.pos, 1) + self.assertEquals(cm.exception.policy, policy) + + def test_concretizer_default(self): + @concretized_args(b='') + def inner_func(self, a, b): + return a, b + + with self.assertRaises(ConcretizeStack) as cm: + inner_func(None, 34, self.bv) + + self.assertEquals(cm.exception.pos, 2) + # Make sure the policy isn't blank, i.e. we didn't pass through + # a falsifiable value, and we selected a default + self.assertTrue(cm.exception.policy) + self.assertNotEquals(cm.exception.policy, '') + + + def test_concretizer_doesnt_overreach(self): + @concretized_args(b='') + def inner_func(self, a, b): + return a, b + + # Make sure we don't raise when a param is symbolic and its concretization + # wasn't requested. + inner_func(None, self.bv, 123) + +