Handles issue with low constructor loop bound and new way to deal with solver-timeout (#1668)

* Add exit code

* Update docs with tutorial

* Fix docs

* Reformat

* Fix the issue with loop-bound and solver-timeout

* Add test changes

* Restrict hexbytes
pull/1669/head
Nikhil Parasaram 2 years ago committed by GitHub
parent 614ea8ccef
commit eb2c11ef01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      mythril/exceptions.py
  2. 9
      mythril/interfaces/cli.py
  3. 14
      mythril/laser/ethereum/state/constraints.py
  4. 2
      mythril/laser/ethereum/strategy/extensions/bounded_loops.py
  5. 9
      mythril/laser/ethereum/svm.py
  6. 2
      mythril/mythril/mythril_analyzer.py
  7. 13
      mythril/support/model.py
  8. 2
      mythril/support/support_args.py
  9. 1
      requirements.txt
  10. 2
      tests/graph_test.py
  11. 2
      tests/mythril/mythril_analyzer_test.py
  12. 2
      tests/statespace_test.py

@ -20,6 +20,13 @@ class UnsatError(MythrilBaseException):
pass pass
class SolverTimeOutException(UnsatError):
"""A Mythril exception denoting the unsatisfiability of a series of
constraints."""
pass
class NoContractFoundError(MythrilBaseException): class NoContractFoundError(MythrilBaseException):
"""A Mythril exception denoting that a given contract file was not """A Mythril exception denoting that a given contract file was not
found.""" found."""

@ -511,9 +511,10 @@ def add_analysis_args(options):
help="Don't attempt to retrieve contract code, variables and balances from the blockchain", help="Don't attempt to retrieve contract code, variables and balances from the blockchain",
) )
options.add_argument( options.add_argument(
"--sparse-pruning", "--pruning-factor",
action="store_true", type=float,
help="Checks for reachability after the end of tx. Recommended for short execution timeouts < 1 min", default=1,
help="Checks for reachability at the rate of <pruning-factor>. Where 1 means checks every execution",
) )
options.add_argument( options.add_argument(
"--unconstrained-storage", "--unconstrained-storage",
@ -752,7 +753,7 @@ def execute_command(
elif args.command == SAFE_FUNCTIONS_COMMAND: elif args.command == SAFE_FUNCTIONS_COMMAND:
args.unconstrained_storage = True args.unconstrained_storage = True
args.sparse_pruning = False args.pruning_factor = 1
args.disable_dependency_pruning = True args.disable_dependency_pruning = True
args.no_onchain_data = True args.no_onchain_data = True
function_analyzer = MythrilAnalyzer( function_analyzer = MythrilAnalyzer(

@ -1,6 +1,6 @@
"""This module contains the class used to represent state-change constraints in """This module contains the class used to represent state-change constraints in
the call graph.""" the call graph."""
from mythril.exceptions import UnsatError from mythril.exceptions import UnsatError, SolverTimeOutException
from mythril.laser.smt import symbol_factory, simplify, Bool from mythril.laser.smt import symbol_factory, simplify, Bool
from mythril.support.model import get_model from mythril.support.model import get_model
from mythril.laser.ethereum.function_managers import keccak_function_manager from mythril.laser.ethereum.function_managers import keccak_function_manager
@ -25,13 +25,19 @@ class Constraints(list):
constraint_list = self._get_smt_bool_list(constraint_list) constraint_list = self._get_smt_bool_list(constraint_list)
super(Constraints, self).__init__(constraint_list) super(Constraints, self).__init__(constraint_list)
@property def is_possible(self, solver_timeout=None) -> bool:
def is_possible(self) -> bool:
""" """
:param solver_timeout: The default timeout uses analysis timeout from args.solver_timeout
:return: True/False based on the existence of solution of constraints :return: True/False based on the existence of solution of constraints
""" """
try: try:
get_model(self) get_model(self, solver_timeout=solver_timeout)
except SolverTimeOutException:
# If it uses the long analysis solver timeout
if solver_timeout is None:
return False
# If it uses a short custom solver timeout
return True
except UnsatError: except UnsatError:
return False return False
return True return True

@ -135,7 +135,7 @@ class BoundedLoopsStrategy(BasicSearchStrategy):
# TODO: There's probably a nicer way to do this # TODO: There's probably a nicer way to do this
if isinstance( if isinstance(
state.current_transaction, ContractCreationTransaction state.current_transaction, ContractCreationTransaction
) and count < max(20, self.bound): ) and count < max(128, self.bound):
return state return state
elif count > self.bound: elif count > self.bound:

@ -3,6 +3,7 @@ import logging
from collections import defaultdict from collections import defaultdict
from copy import copy from copy import copy
from datetime import datetime, timedelta from datetime import datetime, timedelta
import random
from typing import Callable, Dict, DefaultDict, List, Tuple, Optional from typing import Callable, Dict, DefaultDict, List, Tuple, Optional
from mythril.support.opcodes import OPCODES from mythril.support.opcodes import OPCODES
@ -218,7 +219,9 @@ class LaserEVM:
old_states_count = len(self.open_states) old_states_count = len(self.open_states)
if self.use_reachability_check: if self.use_reachability_check:
self.open_states = [ self.open_states = [
state for state in self.open_states if state.constraints.is_possible state
for state in self.open_states
if state.constraints.is_possible()
] ]
prune_count = old_states_count - len(self.open_states) prune_count = old_states_count - len(self.open_states)
if prune_count: if prune_count:
@ -282,11 +285,11 @@ class LaserEVM:
except NotImplementedError: except NotImplementedError:
log.debug("Encountered unimplemented instruction") log.debug("Encountered unimplemented instruction")
continue continue
if args.sparse_pruning is False: if len(new_states) > 1 and random.uniform(0, 1) < args.pruning_factor:
new_states = [ new_states = [
state state
for state in new_states for state in new_states
if state.world_state.constraints.is_possible if state.world_state.constraints.is_possible()
] ]
self.manage_cfg(op_code, new_states) # TODO: What about op_code is None? self.manage_cfg(op_code, new_states) # TODO: What about op_code is None?

@ -60,7 +60,7 @@ class MythrilAnalyzer:
if cmd_args.custom_modules_directory if cmd_args.custom_modules_directory
else "" else ""
) )
args.sparse_pruning = cmd_args.sparse_pruning args.pruning_factor = cmd_args.pruning_factor
args.solver_timeout = cmd_args.solver_timeout args.solver_timeout = cmd_args.solver_timeout
args.parallel_solving = cmd_args.parallel_solving args.parallel_solving = cmd_args.parallel_solving
args.unconstrained_storage = cmd_args.unconstrained_storage args.unconstrained_storage = cmd_args.unconstrained_storage

@ -5,7 +5,7 @@ from pathlib import Path
from mythril.support.support_args import args from mythril.support.support_args import args
from mythril.laser.smt import Optimize from mythril.laser.smt import Optimize
from mythril.laser.ethereum.time_handler import time_handler from mythril.laser.ethereum.time_handler import time_handler
from mythril.exceptions import UnsatError from mythril.exceptions import UnsatError, SolverTimeOutException
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -13,7 +13,13 @@ log = logging.getLogger(__name__)
@lru_cache(maxsize=2**23) @lru_cache(maxsize=2**23)
def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True): def get_model(
constraints,
minimize=(),
maximize=(),
enforce_execution_time=True,
solver_timeout=None,
):
""" """
Returns a model based on given constraints as a tuple Returns a model based on given constraints as a tuple
:param constraints: Tuple of constraints :param constraints: Tuple of constraints
@ -23,7 +29,7 @@ def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True
:return: :return:
""" """
s = Optimize() s = Optimize()
timeout = args.solver_timeout timeout = solver_timeout or args.solver_timeout
if enforce_execution_time: if enforce_execution_time:
timeout = min(timeout, time_handler.time_remaining() - 500) timeout = min(timeout, time_handler.time_remaining() - 500)
if timeout <= 0: if timeout <= 0:
@ -60,4 +66,5 @@ def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True
return s.model() return s.model()
elif result == unknown: elif result == unknown:
log.debug("Timeout/Error encountered while solving expression using z3") log.debug("Timeout/Error encountered while solving expression using z3")
raise SolverTimeOutException
raise UnsatError raise UnsatError

@ -10,7 +10,7 @@ class Args(object, metaclass=Singleton):
def __init__(self): def __init__(self):
self.solver_timeout = 10000 self.solver_timeout = 10000
self.sparse_pruning = True self.pruning_factor = 1
self.unconstrained_storage = False self.unconstrained_storage = False
self.parallel_solving = False self.parallel_solving = False
self.call_depth_limit = 3 self.call_depth_limit = 3

@ -16,6 +16,7 @@ eth-keys<0.4.0,>=0.2.1
eth-rlp<0.3.0,>=0.1.0 eth-rlp<0.3.0,>=0.1.0
eth-typing<3.0.0,>=2.1.0 eth-typing<3.0.0,>=2.1.0
eth-utils<2 eth-utils<2
hexbytes<0.3.0
jinja2>=2.9 jinja2>=2.9
MarkupSafe<2.1.0 MarkupSafe<2.1.0
mock mock

@ -25,7 +25,7 @@ def test_generate_graph():
create_timeout=None, create_timeout=None,
disable_dependency_pruning=False, disable_dependency_pruning=False,
custom_modules_directory=None, custom_modules_directory=None,
sparse_pruning=True, pruning_factor=0,
parallel_solving=True, parallel_solving=True,
unconstrained_storage=True, unconstrained_storage=True,
call_depth_limit=3, call_depth_limit=3,

@ -32,7 +32,7 @@ def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info):
create_timeout=None, create_timeout=None,
disable_dependency_pruning=False, disable_dependency_pruning=False,
custom_modules_directory=None, custom_modules_directory=None,
sparse_pruning=True, pruning_factor=0,
parallel_solving=True, parallel_solving=True,
unconstrained_storage=True, unconstrained_storage=True,
call_depth_limit=3, call_depth_limit=3,

@ -22,7 +22,7 @@ def test_statespace_dump():
create_timeout=None, create_timeout=None,
disable_dependency_pruning=False, disable_dependency_pruning=False,
custom_modules_directory=None, custom_modules_directory=None,
sparse_pruning=True, pruning_factor=0,
parallel_solving=True, parallel_solving=True,
unconstrained_storage=True, unconstrained_storage=True,
call_depth_limit=3, call_depth_limit=3,

Loading…
Cancel
Save