Merge pull request #897 from ConsenSys/enhance/604

Reachability Check
pull/913/head
JoranHonig 6 years ago committed by GitHub
commit adec5f608e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      mythril/analysis/solver.py
  2. 21
      mythril/laser/ethereum/instructions.py
  3. 71
      mythril/laser/ethereum/state/constraints.py
  4. 6
      mythril/laser/ethereum/svm.py
  5. 2
      mythril/laser/ethereum/transaction/symbolic.py
  6. 3
      tests/laser/evm_testsuite/evm_test.py

@ -13,18 +13,21 @@ import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def get_model(constraints, minimize=(), maximize=()): def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True):
""" """
:param constraints: :param constraints:
:param minimize: :param minimize:
:param maximize: :param maximize:
:param enforce_execution_time: Bool variable which enforces --execution-timeout's time
:return: :return:
""" """
s = Optimize() s = Optimize()
timeout = min(100000, time_handler.time_remaining() - 500) timeout = 100000
if timeout <= 0: if enforce_execution_time:
raise UnsatError timeout = min(timeout, time_handler.time_remaining() - 500)
if timeout <= 0:
raise UnsatError
s.set_timeout(timeout) s.set_timeout(timeout)
for constraint in constraints: for constraint in constraints:
if type(constraint) == bool and not constraint: if type(constraint) == bool and not constraint:

@ -1527,9 +1527,18 @@ class Instruction:
simplify(Not(condition)) if isinstance(condition, Bool) else condition == 0 simplify(Not(condition)) if isinstance(condition, Bool) else condition == 0
) )
negated.simplify() negated.simplify()
if (type(negated) == bool and negated) or ( # True case
condi = simplify(condition) if isinstance(condition, Bool) else condition != 0
condi.simplify()
negated_cond = (type(negated) == bool and negated) or (
isinstance(negated, Bool) and not is_false(negated) isinstance(negated, Bool) and not is_false(negated)
): )
positive_cond = (type(condi) == bool and condi) or (
isinstance(condi, Bool) and not is_false(condi)
)
if negated_cond:
new_state = copy(global_state) new_state = copy(global_state)
# add JUMPI gas cost # add JUMPI gas cost
new_state.mstate.min_gas_used += min_gas new_state.mstate.min_gas_used += min_gas
@ -1547,18 +1556,15 @@ class Instruction:
# Get jump destination # Get jump destination
index = util.get_instruction_index(disassembly.instruction_list, jump_addr) index = util.get_instruction_index(disassembly.instruction_list, jump_addr)
if not index: if not index:
log.debug("Invalid jump destination: " + str(jump_addr)) log.debug("Invalid jump destination: " + str(jump_addr))
return states return states
instr = disassembly.instruction_list[index] instr = disassembly.instruction_list[index]
condi = simplify(condition) if isinstance(condition, Bool) else condition != 0
condi.simplify()
if instr["opcode"] == "JUMPDEST": if instr["opcode"] == "JUMPDEST":
if (type(condi) == bool and condi) or ( if positive_cond:
isinstance(condi, Bool) and not is_false(condi)
):
new_state = copy(global_state) new_state = copy(global_state)
# add JUMPI gas cost # add JUMPI gas cost
new_state.mstate.min_gas_used += min_gas new_state.mstate.min_gas_used += min_gas
@ -1571,7 +1577,6 @@ class Instruction:
states.append(new_state) states.append(new_state)
else: else:
log.debug("Pruned unreachable states.") log.debug("Pruned unreachable states.")
del global_state
return states return states
@StateTransition() @StateTransition()

@ -1,77 +1,100 @@
"""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.laser.smt import Solver, Bool
from typing import Iterable, List, Optional
from z3 import unsat
class Constraints(list): class Constraints(list):
"""This class should maintain a solver and it's constraints, This class """This class should maintain a solver and it's constraints, This class
tries to make the Constraints() object as a simple list of constraints with tries to make the Constraints() object as a simple list of constraints with
some background processing. some background processing.
TODO: add the solver to this class after callback refactor
""" """
def __init__(self, constraint_list=None, solver=None, possibility=None): def __init__(
self,
constraint_list: Optional[List[Bool]] = None,
is_possible: Optional[bool] = None,
) -> None:
""" """
:param constraint_list: :param constraint_list: List of constraints
:param solver: :param is_possible: Whether it is possible to satisfy the constraints or not
:param possibility:
""" """
super(Constraints, self).__init__(constraint_list or []) super(Constraints, self).__init__(constraint_list or [])
self.solver = solver self._default_timeout = 100
self.__possibility = possibility self._is_possible = is_possible
def check_possibility(self): @property
def is_possible(self) -> bool:
""" """
:return: True/False based on the existence of solution of constraints
:return:
""" """
return True if self._is_possible is not None:
return self._is_possible
solver = Solver()
solver.set_timeout(self._default_timeout)
for constraint in self[:]:
solver.add(constraint)
self._is_possible = solver.check() != unsat
return self._is_possible
def append(self, constraint): def append(self, constraint: Bool) -> None:
""" """
:param constraint: :param constraint: The constraint to be appended
""" """
super(Constraints, self).append(constraint) super(Constraints, self).append(constraint)
self._is_possible = None
def pop(self, index=-1): def pop(self, index: int = -1) -> None:
""" """
:param index: :param index: Index to be popped from the list
""" """
raise NotImplementedError raise NotImplementedError
def __copy__(self): @property
def as_list(self) -> List[Bool]:
"""
:return: returns the list of constraints
"""
return self[:]
def __copy__(self) -> "Constraints":
""" """
:return: :return: The copied constraint List
""" """
constraint_list = super(Constraints, self).copy() constraint_list = super(Constraints, self).copy()
return Constraints(constraint_list) return Constraints(constraint_list, is_possible=self._is_possible)
def __deepcopy__(self, memodict=None): def __deepcopy__(self, memodict=None) -> "Constraints":
""" """
:param memodict: :param memodict:
:return: :return: The copied constraint List
""" """
return self.__copy__() return self.__copy__()
def __add__(self, constraints): def __add__(self, constraints: List[Bool]) -> "Constraints":
""" """
:param constraints: :param constraints:
:return: :return: the new list after the + operation
""" """
constraints_list = super(Constraints, self).__add__(constraints) constraints_list = super(Constraints, self).__add__(constraints)
return Constraints(constraint_list=constraints_list) return Constraints(constraint_list=constraints_list, is_possible=None)
def __iadd__(self, constraints): def __iadd__(self, constraints: Iterable[Bool]) -> "Constraints":
""" """
:param constraints: :param constraints:
:return: :return:
""" """
super(Constraints, self).__iadd__(constraints) super(Constraints, self).__iadd__(constraints)
self._is_possible = None
return self return self

@ -229,6 +229,11 @@ class LaserEVM:
except NotImplementedError: except NotImplementedError:
log.debug("Encountered unimplemented instruction") log.debug("Encountered unimplemented instruction")
continue continue
new_states = [
state for state in new_states if state.mstate.constraints.is_possible
]
self.manage_cfg(op_code, new_states) self.manage_cfg(op_code, new_states)
if new_states: if new_states:
@ -236,6 +241,7 @@ class LaserEVM:
elif track_gas: elif track_gas:
final_states.append(global_state) final_states.append(global_state)
self.total_states += len(new_states) self.total_states += len(new_states)
return final_states if track_gas else None return final_states if track_gas else None
def _add_world_state(self, global_state: GlobalState): def _add_world_state(self, global_state: GlobalState):

@ -133,7 +133,7 @@ def _setup_global_state_for_execution(laser_evm, transaction) -> None:
) )
global_state.mstate.constraints += transaction.world_state.node.constraints global_state.mstate.constraints += transaction.world_state.node.constraints
new_node.constraints = global_state.mstate.constraints new_node.constraints = global_state.mstate.constraints.as_list
global_state.world_state.transaction_sequence.append(transaction) global_state.world_state.transaction_sequence.append(transaction)
global_state.node = new_node global_state.node = new_node

@ -127,7 +127,8 @@ def test_vmtest(
assert len(laser_evm.open_states) == 1 assert len(laser_evm.open_states) == 1
world_state = laser_evm.open_states[0] world_state = laser_evm.open_states[0]
model = get_model( model = get_model(
next(iter(laser_evm.nodes.values())).states[0].mstate.constraints next(iter(laser_evm.nodes.values())).states[0].mstate.constraints,
enforce_execution_time=False,
) )
for address, details in post_condition.items(): for address, details in post_condition.items():

Loading…
Cancel
Save