diff --git a/.circleci/config.yml b/.circleci/config.yml index 0638f0dd..8b537d6a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -74,6 +74,12 @@ jobs: # command: if [ -z "$CIRCLE_PR_NUMBER" ]; then ./run-integration-tests.sh; fi # working_directory: /home + - run: + name: Call webhook + command: | + curl -I -X POST -H -d "https://circleci.com/api/v1/project/${ORGANIZATION}/${WEBHOOK_PROJECT}/tree/master?circle-token=${CIRCLE_TOKEN}" | head -n 1 | cut -d$' ' -f2 + + pypi_release: <<: *defaults steps: diff --git a/mythril/alarm.py b/mythril/alarm.py new file mode 100644 index 00000000..4c546bf4 --- /dev/null +++ b/mythril/alarm.py @@ -0,0 +1,25 @@ +import signal +from types import FrameType +from mythril.exceptions import OutOfTimeError + + +def sigalrm_handler(signum: int, frame: FrameType) -> None: + raise OutOfTimeError + + +def start_timeout(timeout: int) -> None: + """ + Starts a timeout + :param timeout: Time in seconds to set the timeout for + :return: None + """ + signal.signal(signal.SIGALRM, sigalrm_handler) + signal.alarm(timeout) + + +def disable_timeout() -> None: + """ + Ensures that the timeout is disabled + :return: None + """ + signal.alarm(0) diff --git a/mythril/analysis/modules/base.py b/mythril/analysis/modules/base.py index c6168744..db93ac55 100644 --- a/mythril/analysis/modules/base.py +++ b/mythril/analysis/modules/base.py @@ -48,7 +48,8 @@ class DetectionModule: "DetectionModule " "name={0.name} " "swc_id={0.swc_id} " - "hooks={0.hooks} " + "pre_hooks={0.pre_hooks} " + "post_hooks={0.post_hooks} " "description={0.description}" ">" ).format(self) diff --git a/mythril/analysis/security.py b/mythril/analysis/security.py index 81bb69f0..fb563b20 100644 --- a/mythril/analysis/security.py +++ b/mythril/analysis/security.py @@ -89,6 +89,12 @@ def fire_lasers(statespace, module_names=()): log.info("Executing " + module.detector.name) issues += module.detector.execute(statespace) + issues += retrieve_callback_issues(module_names) + return issues + + +def retrieve_callback_issues(module_names=()): + issues = [] for module in get_detection_modules( entrypoint="callback", include_modules=module_names ): diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index bb7dfd33..a5e6789f 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -1,8 +1,7 @@ """This module contains a wrapper around LASER for extended analysis purposes.""" -import copy -from mythril.analysis.security import get_detection_module_hooks +from mythril.analysis.security import get_detection_module_hooks, get_detection_modules from mythril.laser.ethereum import svm from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.strategy.basic import ( @@ -33,6 +32,7 @@ class SymExecWrapper: create_timeout=None, transaction_count=2, modules=(), + compulsory_statespace=True, ): """ @@ -63,7 +63,9 @@ class SymExecWrapper: dynamic_loader=dynloader, contract_name=contract.name, ) - + requires_statespace = ( + compulsory_statespace or len(get_detection_modules("post", modules)) > 0 + ) self.accounts = {address: account} self.laser = svm.LaserEVM( @@ -74,6 +76,7 @@ class SymExecWrapper: strategy=s_strategy, create_timeout=create_timeout, transaction_count=transaction_count, + requires_statespace=requires_statespace, ) self.laser.register_hooks( hook_type="pre", @@ -95,6 +98,9 @@ class SymExecWrapper: else: self.laser.sym_exec(address) + if not requires_statespace: + return + self.nodes = self.laser.nodes self.edges = self.laser.edges diff --git a/mythril/exceptions.py b/mythril/exceptions.py index 7988759b..49a6ad3c 100644 --- a/mythril/exceptions.py +++ b/mythril/exceptions.py @@ -7,6 +7,10 @@ class MythrilBaseException(Exception): pass +class OutOfTimeError(MythrilBaseException): + pass + + class CompilerError(MythrilBaseException): """A Mythril exception denoting an error during code compilation.""" diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 5d84f781..a376443c 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -11,10 +11,9 @@ from mythril.laser.smt import simplify, Expression, symbol_factory import mythril.laser.ethereum.util as util from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.calldata import ( - CalldataType, + BaseCalldata, SymbolicCalldata, ConcreteCalldata, - BaseCalldata, ) from mythril.laser.ethereum.state.global_state import GlobalState from mythril.support.loader import DynLoader @@ -48,9 +47,7 @@ def get_call_parameters( callee_address = get_callee_address(global_state, dynamic_loader, to) callee_account = None - call_data, call_data_type = get_call_data( - global_state, memory_input_offset, memory_input_size - ) + call_data = get_call_data(global_state, memory_input_offset, memory_input_size) if int(callee_address, 16) >= 5 or int(callee_address, 16) == 0: callee_account = get_callee_account( @@ -62,7 +59,6 @@ def get_call_parameters( callee_account, call_data, value, - call_data_type, gas, memory_out_offset, memory_out_size, @@ -189,14 +185,12 @@ def get_call_data( ) ] call_data = ConcreteCalldata(transaction_id, calldata_from_mem) - call_data_type = CalldataType.CONCRETE log.debug("Calldata: " + str(call_data)) except TypeError: log.debug("Unsupported symbolic calldata offset") - call_data_type = CalldataType.SYMBOLIC call_data = SymbolicCalldata("{}_internalcall".format(transaction_id)) - return call_data, call_data_type + return call_data def native_call( diff --git a/mythril/laser/ethereum/cfg.py b/mythril/laser/ethereum/cfg.py index b3cc3c43..09434d30 100644 --- a/mythril/laser/ethereum/cfg.py +++ b/mythril/laser/ethereum/cfg.py @@ -26,8 +26,13 @@ class NodeFlags(Flags): class Node: """The representation of a call graph node.""" - - def __init__(self, contract_name: str, start_addr=0, constraints=None): + def __init__( + self, + contract_name: str, + start_addr=0, + constraints=None, + function_name="unknown", + ): """ :param contract_name: @@ -39,7 +44,7 @@ class Node: self.start_addr = start_addr self.states = [] self.constraints = constraints - self.function_name = "unknown" + self.function_name = function_name self.flags = NodeFlags() # Self-assign a unique ID diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index d6abcd9c..a88ff31a 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -516,7 +516,9 @@ class Instruction: ) else: - state.stack.append(pow(base.value, exponent.value, 2 ** 256)) + state.stack.append( + symbol_factory.BitVecVal(pow(base.value, exponent.value, 2 ** 256), 256) + ) return [global_state] @@ -1678,7 +1680,7 @@ class Instruction: environment = global_state.environment try: - callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, callee_account, call_data, value, gas, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader, True ) except ValueError as e: @@ -1712,7 +1714,6 @@ class Instruction: ), callee_account=callee_account, call_data=call_data, - call_data_type=call_data_type, call_value=value, ) raise TransactionStartSignal(transaction, self.op_code) @@ -1795,7 +1796,7 @@ class Instruction: environment = global_state.environment try: - callee_address, callee_account, call_data, value, call_data_type, gas, _, _ = get_call_parameters( + callee_address, callee_account, call_data, value, gas, _, _ = get_call_parameters( global_state, self.dynamic_loader, True ) except ValueError as e: @@ -1818,7 +1819,6 @@ class Instruction: caller=environment.address, callee_account=environment.active_account, call_data=call_data, - call_data_type=call_data_type, call_value=value, ) raise TransactionStartSignal(transaction, self.op_code) @@ -1833,7 +1833,7 @@ class Instruction: instr = global_state.get_current_instruction() try: - callee_address, _, _, value, _, _, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, _, _, value, _, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader, True ) except ValueError as e: @@ -1899,7 +1899,7 @@ class Instruction: environment = global_state.environment try: - callee_address, callee_account, call_data, value, call_data_type, gas, _, _ = get_call_parameters( + callee_address, callee_account, call_data, value, gas, _, _ = get_call_parameters( global_state, self.dynamic_loader ) except ValueError as e: @@ -1922,7 +1922,6 @@ class Instruction: caller=environment.sender, callee_account=environment.active_account, call_data=call_data, - call_data_type=call_data_type, call_value=environment.callvalue, ) raise TransactionStartSignal(transaction, self.op_code) @@ -1937,7 +1936,7 @@ class Instruction: instr = global_state.get_current_instruction() try: - callee_address, _, _, value, _, _, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, _, _, value, _, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader ) except ValueError as e: @@ -2002,7 +2001,7 @@ class Instruction: # TODO: implement me instr = global_state.get_current_instruction() try: - callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, callee_account, call_data, value, gas, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader ) except ValueError as e: diff --git a/mythril/laser/ethereum/state/account.py b/mythril/laser/ethereum/state/account.py index 3d13428f..2c69dbc5 100644 --- a/mythril/laser/ethereum/state/account.py +++ b/mythril/laser/ethereum/state/account.py @@ -47,7 +47,7 @@ class Storage: except ValueError: pass if self.concrete: - return 0 + return symbol_factory.BitVecVal(0, 256) self._storage[item] = symbol_factory.BitVecVal(0, 256) return self._storage[item] diff --git a/mythril/laser/ethereum/state/calldata.py b/mythril/laser/ethereum/state/calldata.py index 2be3d888..b2ea15a0 100644 --- a/mythril/laser/ethereum/state/calldata.py +++ b/mythril/laser/ethereum/state/calldata.py @@ -1,4 +1,7 @@ """This module declares classes to represent call data.""" +from typing import Union, Any + +from mythril.laser.smt import K, Array, If, simplify, Concat, Expression, BitVec from enum import Enum from typing import Any, Union @@ -19,11 +22,6 @@ from mythril.laser.smt import ( ) -class CalldataType(Enum): - CONCRETE = 1 - SYMBOLIC = 2 - - class BaseCalldata: """Base calldata class This represents the calldata provided when sending a transaction to a contract.""" diff --git a/mythril/laser/ethereum/state/environment.py b/mythril/laser/ethereum/state/environment.py index a7686ad2..3924a8b3 100644 --- a/mythril/laser/ethereum/state/environment.py +++ b/mythril/laser/ethereum/state/environment.py @@ -5,8 +5,7 @@ from typing import Dict from z3 import ExprRef from mythril.laser.ethereum.state.account import Account -from mythril.laser.ethereum.state.calldata import BaseCalldata, CalldataType -from mythril.laser.smt import symbol_factory +from mythril.laser.ethereum.state.calldata import BaseCalldata class Environment: @@ -22,7 +21,6 @@ class Environment: callvalue: ExprRef, origin: ExprRef, code=None, - calldata_type=CalldataType.SYMBOLIC, ): """ @@ -47,7 +45,6 @@ class Environment: self.sender = sender self.calldata = calldata - self.calldata_type = calldata_type self.gasprice = gasprice self.origin = origin self.callvalue = callvalue @@ -72,5 +69,4 @@ class Environment: gasprice=self.gasprice, callvalue=self.callvalue, origin=self.origin, - calldata_type=self.calldata_type, ) diff --git a/mythril/laser/ethereum/state/memory.py b/mythril/laser/ethereum/state/memory.py index a01ae967..64be50f3 100644 --- a/mythril/laser/ethereum/state/memory.py +++ b/mythril/laser/ethereum/state/memory.py @@ -43,8 +43,12 @@ class Memory: :return: 32 byte word at the specified index """ try: - return util.concrete_int_from_bytes( - bytes([util.get_concrete_int(b) for b in self[index : index + 32]]), 0 + return symbol_factory.BitVecVal( + util.concrete_int_from_bytes( + bytes([util.get_concrete_int(b) for b in self[index : index + 32]]), + 0, + ), + 256, ) except: result = simplify( diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 0bd646c1..69def19e 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -6,8 +6,11 @@ from datetime import datetime, timedelta from functools import reduce from typing import Callable, Dict, List, Tuple, Union -from mythril.laser.ethereum.cfg import Edge, JumpType, Node, NodeFlags -from mythril.laser.ethereum.evm_exceptions import StackUnderflowException, VmException +from mythril import alarm +from mythril.exceptions import OutOfTimeError +from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType +from mythril.laser.ethereum.evm_exceptions import StackUnderflowException +from mythril.laser.ethereum.evm_exceptions import VmException from mythril.laser.ethereum.instructions import Instruction from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.global_state import GlobalState @@ -50,6 +53,7 @@ class LaserEVM: create_timeout=10, strategy=DepthFirstSearchStrategy, transaction_count=2, + requires_statespace=True, ): """ @@ -67,8 +71,6 @@ class LaserEVM: self.world_state = world_state self.open_states = [world_state] - self.nodes = {} - self.edges = [] self.coverage = {} self.total_states = 0 @@ -79,9 +81,14 @@ class LaserEVM: self.max_depth = max_depth self.transaction_count = transaction_count - self.execution_timeout = execution_timeout + self.execution_timeout = execution_timeout or 0 self.create_timeout = create_timeout + self.requires_statespace = requires_statespace + if self.requires_statespace: + self.nodes = {} + self.edges = [] + self.time = None self.pre_hooks = defaultdict(list) @@ -125,37 +132,46 @@ class LaserEVM: :param contract_name: """ log.debug("Starting LASER execution") - self.time = datetime.now() - if main_address: - log.info("Starting message call transaction to {}".format(main_address)) - self._execute_transactions(main_address) + try: + alarm.start_timeout(self.execution_timeout) + self.time = datetime.now() - elif creation_code: - log.info("Starting contract creation transaction") - created_account = execute_contract_creation( - self, creation_code, contract_name - ) - log.info( - "Finished contract creation, found {} open states".format( - len(self.open_states) + if main_address: + log.info("Starting message call transaction to {}".format(main_address)) + self._execute_transactions(main_address) + + elif creation_code: + log.info("Starting contract creation transaction") + created_account = execute_contract_creation( + self, creation_code, contract_name ) - ) - if len(self.open_states) == 0: - log.warning( - "No contract was created during the execution of contract creation " - "Increase the resources for creation execution (--max-depth or --create-timeout)" + log.info( + "Finished contract creation, found {} open states".format( + len(self.open_states) + ) ) + if len(self.open_states) == 0: + log.warning( + "No contract was created during the execution of contract creation " + "Increase the resources for creation execution (--max-depth or --create-timeout)" + ) + + self._execute_transactions(created_account.address) - self._execute_transactions(created_account.address) + except OutOfTimeError: + log.warning("Timeout occurred, ending symbolic execution") + finally: + alarm.disable_timeout() log.info("Finished symbolic execution") - log.info( - "%d nodes, %d edges, %d total states", - len(self.nodes), - len(self.edges), - self.total_states, - ) + if self.requires_statespace: + log.info( + "%d nodes, %d edges, %d total states", + len(self.nodes), + len(self.edges), + self.total_states, + ) for code, coverage in self.coverage.items(): cov = ( reduce(lambda sum_, val: sum_ + 1 if val else sum_, coverage[1]) @@ -176,7 +192,11 @@ class LaserEVM: initial_coverage = self._get_covered_instructions() self.time = datetime.now() - log.info("Starting message call transaction, iteration: {}".format(i)) + log.info( + "Starting message call transaction, iteration: {}, {} initial states".format( + i, len(self.open_states) + ) + ) execute_message_call(self, address) @@ -209,15 +229,12 @@ class LaserEVM: """ final_states = [] for global_state in self.strategy: - if self.execution_timeout and not create: - if ( - self.time + timedelta(seconds=self.execution_timeout) - <= datetime.now() - ): - return final_states + [global_state] if track_gas else None - elif self.create_timeout and create: - if self.time + timedelta(seconds=self.create_timeout) <= datetime.now(): - return final_states + [global_state] if track_gas else None + if ( + self.create_timeout + and create + and self.time + timedelta(seconds=self.create_timeout) <= datetime.now() + ): + return final_states + [global_state] if track_gas else None try: new_states, op_code = self.execute_state(global_state) @@ -423,10 +440,13 @@ class LaserEVM: old_node = state.node state.node = new_node new_node.constraints = state.mstate.constraints - self.nodes[new_node.uid] = new_node - self.edges.append( - Edge(old_node.uid, new_node.uid, edge_type=edge_type, condition=condition) - ) + if self.requires_statespace: + self.nodes[new_node.uid] = new_node + self.edges.append( + Edge( + old_node.uid, new_node.uid, edge_type=edge_type, condition=condition + ) + ) if edge_type == JumpType.RETURN: new_node.flags |= NodeFlags.CALL_RETURN diff --git a/mythril/laser/ethereum/transaction/concolic.py b/mythril/laser/ethereum/transaction/concolic.py index 173f59df..d8c251dd 100644 --- a/mythril/laser/ethereum/transaction/concolic.py +++ b/mythril/laser/ethereum/transaction/concolic.py @@ -4,7 +4,7 @@ from typing import List, Union from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum.cfg import Node, Edge, JumpType -from mythril.laser.ethereum.state.calldata import CalldataType, ConcreteCalldata +from mythril.laser.ethereum.state.calldata import ConcreteCalldata from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.transaction.transaction_models import ( MessageCallTransaction, @@ -54,7 +54,6 @@ def execute_message_call( caller=caller_address, callee_account=open_world_state[callee_address], call_data=ConcreteCalldata(next_transaction_id, data), - call_data_type=CalldataType.SYMBOLIC, call_value=value, ) @@ -73,10 +72,14 @@ def _setup_global_state_for_execution(laser_evm, transaction) -> None: global_state = transaction.initial_global_state() global_state.transaction_stack.append((transaction, None)) - new_node = Node(global_state.environment.active_account.contract_name) + new_node = Node( + global_state.environment.active_account.contract_name, + function_name=global_state.environment.active_function_name, + ) - laser_evm.nodes[new_node.uid] = new_node - if transaction.world_state.node: + if laser_evm.requires_statespace: + laser_evm.nodes[new_node.uid] = new_node + if transaction.world_state.node and laser_evm.requires_statespace: laser_evm.edges.append( Edge( transaction.world_state.node.uid, @@ -85,6 +88,7 @@ def _setup_global_state_for_execution(laser_evm, transaction) -> None: condition=None, ) ) + global_state.node = new_node new_node.states.append(global_state) laser_evm.work_list.append(global_state) diff --git a/mythril/laser/ethereum/transaction/symbolic.py b/mythril/laser/ethereum/transaction/symbolic.py index 07a19b09..ab39610d 100644 --- a/mythril/laser/ethereum/transaction/symbolic.py +++ b/mythril/laser/ethereum/transaction/symbolic.py @@ -6,11 +6,7 @@ import logging from mythril.laser.smt import symbol_factory from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum.cfg import Node, Edge, JumpType -from mythril.laser.ethereum.state.calldata import ( - CalldataType, - BaseCalldata, - SymbolicCalldata, -) +from mythril.laser.ethereum.state.calldata import BaseCalldata, SymbolicCalldata from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.transaction.transaction_models import ( MessageCallTransaction, @@ -53,7 +49,6 @@ def execute_message_call(laser_evm, callee_address: str) -> None: caller=symbol_factory.BitVecVal(ATTACKER_ADDRESS, 256), callee_account=open_world_state[callee_address], call_data=SymbolicCalldata(next_transaction_id), - call_data_type=CalldataType.SYMBOLIC, call_value=symbol_factory.BitVecSym( "call_value{}".format(next_transaction_id), 256 ), @@ -99,7 +94,6 @@ def execute_contract_creation( caller=symbol_factory.BitVecVal(CREATOR_ADDRESS, 256), callee_account=new_account, call_data=[], - call_data_type=CalldataType.SYMBOLIC, call_value=symbol_factory.BitVecSym( "call_value{}".format(next_transaction_id), 256 ), @@ -120,18 +114,23 @@ def _setup_global_state_for_execution(laser_evm, transaction) -> None: global_state = transaction.initial_global_state() global_state.transaction_stack.append((transaction, None)) - new_node = Node(global_state.environment.active_account.contract_name) + new_node = Node( + global_state.environment.active_account.contract_name, + function_name=global_state.environment.active_function_name, + ) + if laser_evm.requires_statespace: + laser_evm.nodes[new_node.uid] = new_node - laser_evm.nodes[new_node.uid] = new_node if transaction.world_state.node: - laser_evm.edges.append( - Edge( - transaction.world_state.node.uid, - new_node.uid, - edge_type=JumpType.Transaction, - condition=None, + if laser_evm.requires_statespace: + laser_evm.edges.append( + Edge( + transaction.world_state.node.uid, + new_node.uid, + edge_type=JumpType.Transaction, + condition=None, + ) ) - ) global_state.mstate.constraints += transaction.world_state.node.constraints new_node.constraints = global_state.mstate.constraints diff --git a/mythril/laser/ethereum/transaction/transaction_models.py b/mythril/laser/ethereum/transaction/transaction_models.py index d5cfa41b..263934ac 100644 --- a/mythril/laser/ethereum/transaction/transaction_models.py +++ b/mythril/laser/ethereum/transaction/transaction_models.py @@ -2,11 +2,11 @@ execution.""" import array -from typing import Union - from z3 import ExprRef +from typing import Union -from mythril.disassembler.disassembly import Disassembly +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.calldata import BaseCalldata, SymbolicCalldata from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.calldata import BaseCalldata, SymbolicCalldata from mythril.laser.ethereum.state.environment import Environment @@ -61,7 +61,6 @@ class BaseTransaction: gas_limit=None, origin=None, code=None, - call_data_type=None, call_value=None, init_call_data=True, ): @@ -90,11 +89,6 @@ class BaseTransaction: else: self.call_data = call_data if isinstance(call_data, BaseCalldata) else None - self.call_data_type = ( - call_data_type - if call_data_type is not None - else symbol_factory.BitVecSym("call_data_type{}".format(identifier), 256) - ) self.call_value = ( call_value if call_value is not None @@ -132,7 +126,6 @@ class MessageCallTransaction(BaseTransaction): self.call_value, self.origin, code=self.code or self.callee_account.code, - calldata_type=self.call_data_type, ) return super().initial_global_state_from_environment( environment, active_function="fallback" @@ -169,7 +162,6 @@ class ContractCreationTransaction(BaseTransaction): self.call_value, self.origin, self.code, - calldata_type=self.call_data_type, ) return super().initial_global_state_from_environment( environment, active_function="constructor" diff --git a/mythril/mythril.py b/mythril/mythril.py index bd42de92..ddf53425 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -10,7 +10,7 @@ import logging import os import platform import re -from configparser import ConfigParser +import traceback from pathlib import Path from shutil import copyfile @@ -32,7 +32,13 @@ from mythril.exceptions import CompilerError, CriticalError, NoContractFoundErro from mythril.solidity.soliditycontract import SolidityContract, get_contracts_from_file from mythril.support import signatures from mythril.support.loader import DynLoader -from mythril.support.truffle import analyze_truffle_project +from mythril.exceptions import CompilerError, NoContractFoundError, CriticalError +from mythril.analysis.symbolic import SymExecWrapper +from mythril.analysis.callgraph import generate_graph +from mythril.analysis.traceexplore import get_serializable_statespace +from mythril.analysis.security import fire_lasers, retrieve_callback_issues +from mythril.analysis.report import Report +from mythril.ethereum.interface.leveldb.client import EthLevelDB log = logging.getLogger(__name__) @@ -558,23 +564,33 @@ class Mythril(object): """ all_issues = [] for contract in contracts or self.contracts: - sym = SymExecWrapper( - contract, - address, - strategy, - dynloader=DynLoader( - self.eth, - storage_loading=self.onchain_storage_access, - contract_loading=self.dynld, - ), - max_depth=max_depth, - execution_timeout=execution_timeout, - create_timeout=create_timeout, - transaction_count=transaction_count, - modules=modules, - ) - - issues = fire_lasers(sym, modules) + try: + sym = SymExecWrapper( + contract, + address, + strategy, + dynloader=DynLoader( + self.eth, + storage_loading=self.onchain_storage_access, + contract_loading=self.dynld, + ), + max_depth=max_depth, + execution_timeout=execution_timeout, + create_timeout=create_timeout, + transaction_count=transaction_count, + modules=modules, + compulsory_statespace=False, + ) + issues = fire_lasers(sym, modules) + except KeyboardInterrupt: + log.critical("Keyboard Interrupt") + issues = retrieve_callback_issues(modules) + except Exception: + log.critical( + "Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n" + + traceback.format_exc() + ) + issues = retrieve_callback_issues(modules) if type(contract) == SolidityContract: for issue in issues: diff --git a/mythril/support/truffle.py b/mythril/support/truffle.py index 57e101f5..3cfd7406 100644 --- a/mythril/support/truffle.py +++ b/mythril/support/truffle.py @@ -63,6 +63,8 @@ def analyze_truffle_project(sigs, args): create_timeout=args.create_timeout, execution_timeout=args.execution_timeout, transaction_count=args.transaction_count, + modules=args.modules or [], + compulsory_statespace=False, ) issues = fire_lasers(sym) diff --git a/requirements.txt b/requirements.txt index c34147e3..815a2771 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ eth-hash>=0.1.0 eth-keyfile>=0.5.1 eth-keys>=0.2.0b3 eth-rlp>=0.1.0 -eth-tester>=0.1.0b21 +eth-tester==0.1.0b32 eth-typing>=2.0.0 eth-utils>=1.0.1 jinja2>=2.9 diff --git a/setup.py b/setup.py index 98a4935b..d90e08d1 100755 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ setup( "eth-keyfile>=0.5.1", "eth-keys>=0.2.0b3", "eth-rlp>=0.1.0", - "eth-tester>=0.1.0b21", + "eth-tester==0.1.0b32", "eth-typing>=2.0.0", "coverage", "jinja2>=2.9", diff --git a/tests/native_test.py b/tests/native_test.py index fdfb392a..3d0438ff 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -75,13 +75,8 @@ def _all_info(laser): def _test_natives(laser_info, test_list, test_name): - success = 0 for i, j in test_list: - if (str(i) in laser_info or str(int(i, 16)) in laser_info) == j: - success += 1 - else: - print("Failed:", str(int(i, 16)), str(j)) - assert success == len(test_list) + assert (str(i) in laser_info or str(int(i, 16)) in laser_info) == j class NativeTests(BaseTestCase): @@ -99,7 +94,6 @@ class NativeTests(BaseTestCase): laser_info = str(_all_info(laser)) - print(laser_info) _test_natives(laser_info, SHA256_TEST, "SHA256") _test_natives(laser_info, RIPEMD160_TEST, "RIPEMD160") _test_natives(laser_info, ECRECOVER_TEST, "ECRECOVER")