Graceful exit when no states to run (#815)

* rm unnecessary return, change assert to graceful handle

* Add regression test

* Add regression test

* Correct logic

* Rm unnecessary check

This branch is unnecessary, if this condition is true, then ultimately
the executor will do nothing, and we'll simply return anyway.

* Raise NoAliveStates if no alive states to execute

* docstr update

* Update mult_tx_analysis to handle NoAliveStates

* Fancy

* Update test

* Codeclimate fmt

* fmt

* better readability

* add docstr
This commit is contained in:
Mark Mossberg 2018-03-16 17:48:36 -04:00 committed by GitHub
parent a21c8b6012
commit fb79127bc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 8 deletions

View File

@ -1,6 +1,7 @@
import string import string
from . import Manticore from . import Manticore
from .manticore import ManticoreError
from .core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant, operators from .core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant, operators
from .core.smtlib.visitors import arithmetic_simplifier from .core.smtlib.visitors import arithmetic_simplifier
from .platforms import evm from .platforms import evm
@ -22,6 +23,14 @@ logger = logging.getLogger(__name__)
################ Detectors #################### ################ Detectors ####################
class EthereumError(ManticoreError):
pass
class NoAliveStates(EthereumError):
pass
class Detector(Plugin): class Detector(Plugin):
@property @property
def name(self): def name(self):
@ -872,7 +881,7 @@ class ManticoreEVM(Manticore):
return address return address
def transaction(self, caller, address, value, data): def transaction(self, caller, address, value, data):
''' Issue a transaction ''' Issue a symbolic transaction
:param caller: the address of the account sending the transaction :param caller: the address of the account sending the transaction
:type caller: int or EVMAccount :type caller: int or EVMAccount
@ -882,12 +891,18 @@ class ManticoreEVM(Manticore):
:type value: int or SValue :type value: int or SValue
:param data: initial data :param data: initial data
:return: an EVMAccount :return: an EVMAccount
:raises NoAliveStates: if there are no alive states to execute
''' '''
if isinstance(address, EVMAccount): if isinstance(address, EVMAccount):
address = int(address) address = int(address)
if isinstance(caller, EVMAccount): if isinstance(caller, EVMAccount):
caller = int(caller) caller = int(caller)
with self.locked_context('seth') as context:
has_alive_states = context['_saved_states'] or self.initial_state is not None
if not has_alive_states:
raise NoAliveStates
if isinstance(data, self.SByte): if isinstance(data, self.SByte):
data = (None,) * data.size data = (None,) * data.size
with self.locked_context('seth') as context: with self.locked_context('seth') as context:
@ -925,7 +940,10 @@ class ManticoreEVM(Manticore):
current_coverage = 0 current_coverage = 0
while current_coverage < 100: while current_coverage < 100:
run_symbolic_tx() try:
run_symbolic_tx()
except NoAliveStates:
break
if tx_limit is not None: if tx_limit is not None:
tx_limit -= 1 tx_limit -= 1
@ -947,8 +965,7 @@ class ManticoreEVM(Manticore):
# Check if there is a pending transaction # Check if there is a pending transaction
with self.locked_context('seth') as context: with self.locked_context('seth') as context:
assert context['_pending_transaction'] is not None assert context['_pending_transaction'] is not None
# there is at least one states in seth saved states
assert context['_saved_states'] or self.initial_state is not None
# there is no states added to the executor queue # there is no states added to the executor queue
assert len(self._executor.list()) == 0 assert len(self._executor.list()) == 0
@ -958,7 +975,7 @@ class ManticoreEVM(Manticore):
# A callback will use _pending_transaction and issue the transaction # A callback will use _pending_transaction and issue the transaction
# in each state (see load_state_callback) # in each state (see load_state_callback)
result = super(ManticoreEVM, self).run(**kwargs) super(ManticoreEVM, self).run(**kwargs)
with self.locked_context('seth') as context: with self.locked_context('seth') as context:
if len(context['_saved_states']) == 1: if len(context['_saved_states']) == 1:
@ -968,7 +985,6 @@ class ManticoreEVM(Manticore):
# clear pending transcations. We are done. # clear pending transcations. We are done.
context['_pending_transaction'] = None context['_pending_transaction'] = None
return result
def save(self, state, final=False): def save(self, state, final=False):
''' Save a state in secondary storage and add it to running or final lists ''' Save a state in secondary storage and add it to running or final lists

View File

@ -33,6 +33,13 @@ logger = logging.getLogger(__name__)
log.init_logging() log.init_logging()
class ManticoreError(Exception):
"""
Top level Exception object for custom exception hierarchy
"""
pass
def make_binja(program, disasm, argv, env, symbolic_files, concrete_start=''): def make_binja(program, disasm, argv, env, symbolic_files, concrete_start=''):
def _check_disassembler_present(disasm): def _check_disassembler_present(disasm):
if is_binja_disassembler(disasm): if is_binja_disassembler(disasm):

7
tests/binaries/795.sol Normal file
View File

@ -0,0 +1,7 @@
contract Simple {
function f(uint a) payable public {
if (a == 65) {
revert();
}
}
}

View File

@ -128,7 +128,7 @@ class IntegrationTest(unittest.TestCase):
self.assertTrue(len(actual) > 100 ) self.assertTrue(len(actual) > 100 )
def test_eth_regressions(self): def test_eth_regressions(self):
contracts = [676, 678, 701, 714, 735, 760, 780] contracts = [676, 678, 701, 714, 735, 760, 780, 795]
for contract in contracts: for contract in contracts:
self._simple_cli_run('{}.sol'.format(contract)) self._simple_cli_run('{}.sol'.format(contract))

View File

@ -4,7 +4,7 @@ import os
from manticore.core.smtlib import ConstraintSet, operators from manticore.core.smtlib import ConstraintSet, operators
from manticore.core.smtlib.expression import BitVec from manticore.core.smtlib.expression import BitVec
from manticore.core.state import State from manticore.core.state import State
from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector, NoAliveStates
from manticore.platforms.evm import EVMWorld, ConcretizeStack, concretized_args from manticore.platforms.evm import EVMWorld, ConcretizeStack, concretized_args
@ -92,6 +92,31 @@ class EthTests(unittest.TestCase):
self.assertIn('STOP', context) self.assertIn('STOP', context)
self.assertIn('REVERT', context) self.assertIn('REVERT', context)
def test_graceful_handle_no_alive_states(self):
"""
If there are no alive states, or no initial states, we should not crash. issue #795
"""
# initiate the blockchain
m = ManticoreEVM()
source_code = '''
contract Simple {
function f(uint a) payable public {
if (a == 65) {
revert();
}
}
}
'''
# Initiate the accounts
user_account = m.create_account(balance=1000)
contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0)
contract_account.f(1) # it works
contract_account.f(65) # it works
with self.assertRaises(NoAliveStates):
contract_account.f(m.SValue) # no alive states, but try to run a tx anyway
def test_can_create(self): def test_can_create(self):
mevm = ManticoreEVM() mevm = ManticoreEVM()
source_code = """ source_code = """