From d7f80023d7eaa52b4b98f4860bfe6fa13664670c Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Sun, 23 Dec 2018 22:37:07 +0100 Subject: [PATCH] Merge branch 'develop' into feature/docs --- README.md | 2 +- mythril/analysis/modules/base.py | 16 +- mythril/analysis/modules/delegatecall.py | 4 +- .../modules/dependence_on_predictable_vars.py | 2 +- mythril/analysis/modules/deprecated_ops.py | 4 +- mythril/analysis/modules/ether_thief.py | 2 +- mythril/analysis/modules/exceptions.py | 2 +- mythril/analysis/modules/external_calls.py | 9 +- mythril/analysis/modules/integer.py | 448 ++++++------------ mythril/analysis/modules/multiple_sends.py | 171 ++++--- mythril/analysis/modules/suicide.py | 7 +- .../modules/transaction_order_dependence.py | 1 - mythril/analysis/modules/unchecked_retval.py | 160 +++---- mythril/analysis/security.py | 22 +- mythril/analysis/solver.py | 4 +- mythril/analysis/symbolic.py | 7 +- mythril/laser/ethereum/call.py | 55 ++- mythril/laser/ethereum/instructions.py | 125 +++-- mythril/laser/ethereum/state/account.py | 11 +- mythril/laser/ethereum/state/machine_state.py | 4 +- mythril/laser/smt/__init__.py | 26 + tests/graph_test.py | 1 + tests/native_test.py | 6 +- .../outputs_expected/calls.sol.o.json | 103 +++- .../outputs_expected/calls.sol.o.markdown | 16 +- .../outputs_expected/calls.sol.o.text | 16 +- .../kinds_of_calls.sol.o.json | 36 +- .../kinds_of_calls.sol.o.markdown | 30 +- .../kinds_of_calls.sol.o.text | 28 +- .../outputs_expected/overflow.sol.o.json | 68 +-- .../outputs_expected/overflow.sol.o.markdown | 20 +- .../outputs_expected/overflow.sol.o.text | 19 +- .../outputs_expected/returnvalue.sol.o.json | 43 +- .../returnvalue.sol.o.markdown | 4 +- .../outputs_expected/returnvalue.sol.o.text | 4 +- .../outputs_expected/underflow.sol.o.json | 68 +-- .../outputs_expected/underflow.sol.o.markdown | 20 +- .../outputs_expected/underflow.sol.o.text | 19 +- 38 files changed, 843 insertions(+), 740 deletions(-) diff --git a/README.md b/README.md index 6fd15012..e7fd0c8a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Mythril Classic is an open-source security analysis tool for Ethereum smart cont Whether you want to contribute, need support, or want to learn what we have cooking for the future, our [Discord server](https://discord.gg/E3YrVtG) will serve your needs. -Oh and by the way, we're also building an easy-to-use security analysis platform (a.k.a. "the INFURA for smart contract security") that anybody can use to create purpose-built security tools. It's called [Mythril Platform](https://mythril.ai) and you should definitely [check it out](https://media.consensys.net/mythril-platform-api-is-upping-the-smart-contract-security-game-eee1d2642488). +Oh and by the way, we're also building an easy-to-use security analysis platform (a.k.a. "the INFURA for smart contract security") that anybody can use to create purpose-built security tools. It's called [MythX](https://mythril.ai) and you should definitely [check it out](https://media.consensys.net/mythril-platform-api-is-upping-the-smart-contract-security-game-eee1d2642488). ## Installation and setup diff --git a/mythril/analysis/modules/base.py b/mythril/analysis/modules/base.py index 797a3359..c6168744 100644 --- a/mythril/analysis/modules/base.py +++ b/mythril/analysis/modules/base.py @@ -17,21 +17,15 @@ class DetectionModule: self, name: str, swc_id: str, - hooks: List[str], description: str, entrypoint: str = "post", - ) -> None: - """Initialize a new detection module. - - :param name: - :param swc_id: - :param hooks: - :param description: - :param entrypoint: - """ + pre_hooks: List[str] = None, + post_hooks: List[str] = None, + ): self.name = name self.swc_id = swc_id - self.hooks = hooks + self.pre_hooks = pre_hooks if pre_hooks else [] + self.post_hooks = post_hooks if post_hooks else [] self.description = description if entrypoint not in ("post", "callback"): log.error( diff --git a/mythril/analysis/modules/delegatecall.py b/mythril/analysis/modules/delegatecall.py index 53991164..48f9a51d 100644 --- a/mythril/analysis/modules/delegatecall.py +++ b/mythril/analysis/modules/delegatecall.py @@ -22,9 +22,9 @@ class DelegateCallModule(DetectionModule): super().__init__( name="DELEGATECALL Usage in Fallback Function", swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT, - hooks=["DELEGATECALL"], description="Check for invocations of delegatecall(msg.data) in the fallback function.", entrypoint="callback", + pre_hooks=["DELEGATECALL"], ) self._issues = [] @@ -53,6 +53,8 @@ def _analyze_states(state: GlobalState) -> List[Issue]: :return: returns the issues for that corresponding state """ call = get_call_from_state(state) + if call is None: + return [] issues = [] if call.type is not "DELEGATECALL": diff --git a/mythril/analysis/modules/dependence_on_predictable_vars.py b/mythril/analysis/modules/dependence_on_predictable_vars.py index c32cf3cb..47459961 100644 --- a/mythril/analysis/modules/dependence_on_predictable_vars.py +++ b/mythril/analysis/modules/dependence_on_predictable_vars.py @@ -24,13 +24,13 @@ class PredictableDependenceModule(DetectionModule): super().__init__( name="Dependence of Predictable Variables", swc_id="{} {}".format(TIMESTAMP_DEPENDENCE, PREDICTABLE_VARS_DEPENDENCE), - hooks=["CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"], description=( "Check for CALLs that send >0 Ether as a result of computation " "based on predictable variables such as block.coinbase, " "block.gaslimit, block.timestamp, block.number" ), entrypoint="callback", + pre_hooks=["CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"], ) self._issues = [] diff --git a/mythril/analysis/modules/deprecated_ops.py b/mythril/analysis/modules/deprecated_ops.py index 27e5a8be..ed993f93 100644 --- a/mythril/analysis/modules/deprecated_ops.py +++ b/mythril/analysis/modules/deprecated_ops.py @@ -4,7 +4,9 @@ import logging from mythril.analysis.modules.base import DetectionModule from mythril.analysis.report import Issue from mythril.analysis.swc_data import DEPRICATED_FUNCTIONS_USAGE +from mythril.analysis.modules.base import DetectionModule from mythril.laser.ethereum.state.global_state import GlobalState +import logging log = logging.getLogger(__name__) @@ -65,9 +67,9 @@ class DeprecatedOperationsModule(DetectionModule): super().__init__( name="Deprecated Operations", swc_id=DEPRICATED_FUNCTIONS_USAGE, - hooks=["ORIGIN", "CALLCODE"], description=DESCRIPTION, entrypoint="callback", + pre_hooks=["ORIGIN", "CALLCODE"], ) self._issues = [] diff --git a/mythril/analysis/modules/ether_thief.py b/mythril/analysis/modules/ether_thief.py index 0c9b115d..cbc1f35d 100644 --- a/mythril/analysis/modules/ether_thief.py +++ b/mythril/analysis/modules/ether_thief.py @@ -92,9 +92,9 @@ class EtherThief(DetectionModule): super().__init__( name="Ether Thief", swc_id=UNPROTECTED_ETHER_WITHDRAWAL, - hooks=["CALL"], description=DESCRIPTION, entrypoint="callback", + pre_hooks=["CALL"], ) self._issues = [] diff --git a/mythril/analysis/modules/exceptions.py b/mythril/analysis/modules/exceptions.py index 6404e050..ddd2cd95 100644 --- a/mythril/analysis/modules/exceptions.py +++ b/mythril/analysis/modules/exceptions.py @@ -63,9 +63,9 @@ class ReachableExceptionsModule(DetectionModule): super().__init__( name="Reachable Exceptions", swc_id=ASSERT_VIOLATION, - hooks=["ASSERT_FAIL"], description="Checks whether any exception states are reachable.", entrypoint="callback", + pre_hooks=["ASSERT_FAIL"], ) self._issues = [] diff --git a/mythril/analysis/modules/external_calls.py b/mythril/analysis/modules/external_calls.py index 8e316b79..eec11652 100644 --- a/mythril/analysis/modules/external_calls.py +++ b/mythril/analysis/modules/external_calls.py @@ -3,12 +3,13 @@ calls.""" import logging from mythril.analysis import solver +from mythril.analysis.swc_data import REENTRANCY from mythril.analysis.modules.base import DetectionModule from mythril.analysis.report import Issue -from mythril.analysis.swc_data import REENTRANCY -from mythril.exceptions import UnsatError -from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.smt import UGT, symbol_factory +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.exceptions import UnsatError +import logging log = logging.getLogger(__name__) @@ -106,9 +107,9 @@ class ExternalCalls(DetectionModule): super().__init__( name="External calls", swc_id=REENTRANCY, - hooks=["CALL"], description=DESCRIPTION, entrypoint="callback", + pre_hooks=["CALL"], ) self._issues = [] diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index 26c5465a..983a9d1e 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -1,26 +1,36 @@ """This module contains the detection code for integer overflows and underflows.""" -import copy -import logging - from mythril.analysis import solver -from mythril.analysis.modules.base import DetectionModule from mythril.analysis.report import Issue from mythril.analysis.swc_data import INTEGER_OVERFLOW_AND_UNDERFLOW from mythril.exceptions import UnsatError -from mythril.laser.ethereum.taint_analysis import TaintRunner +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.analysis.modules.base import DetectionModule + + from mythril.laser.smt import ( - BitVec, BVAddNoOverflow, - BVMulNoOverflow, BVSubNoUnderflow, - Not, + BVMulNoOverflow, + BitVec, symbol_factory, + Not, + Expression, ) +import logging + + log = logging.getLogger(__name__) +class OverUnderflowAnnotation: + def __init__(self, overflowing_state: GlobalState, operator: str, constraint): + self.overflowing_state = overflowing_state + self.operator = operator + self.constraint = constraint + + class IntegerOverflowUnderflowModule(DetectionModule): """This module searches for integer over- and underflows.""" @@ -29,331 +39,179 @@ class IntegerOverflowUnderflowModule(DetectionModule): super().__init__( name="Integer Overflow and Underflow", swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, - hooks=["ADD", "MUL"], description=( "For every SUB instruction, check if there's a possible state " "where op1 > op0. For every ADD, MUL instruction, check if " "there's a possible state where op1 + op0 > 2^32 - 1" ), + entrypoint="callback", + pre_hooks=["ADD", "MUL", "SUB", "SSTORE", "JUMPI"], ) + self._issues = [] + + @property + def issues(self): + return self._issues - def execute(self, statespace): + def execute(self, state: GlobalState): """Executes analysis module for integer underflow and integer overflow. - :param statespace: Statespace to analyse + :param state: Statespace to analyse :return: Found issues """ - log.debug("Executing module: INTEGER") - - issues = [] + if state.get_current_instruction()["opcode"] == "ADD": + self._handle_add(state) + elif state.get_current_instruction()["opcode"] == "MUL": + self._handle_mul(state) + elif state.get_current_instruction()["opcode"] == "SUB": + self._handle_sub(state) + elif state.get_current_instruction()["opcode"] == "SSTORE": + self._handle_sstore(state) + elif state.get_current_instruction()["opcode"] == "JUMPI": + self._handle_jumpi(state) + + def _handle_add(self, state): + stack = state.mstate.stack + op0, op1 = ( + self._make_bitvec_if_not(stack, -1), + self._make_bitvec_if_not(stack, -2), + ) + c = Not(BVAddNoOverflow(op0, op1, False)) - for k in statespace.nodes: - node = statespace.nodes[k] + # Check satisfiable + model = self._try_constraints(state.node.constraints, [c]) + if model is None: + return - for state in node.states: - issues += self._check_integer_underflow(statespace, state, node) - issues += self._check_integer_overflow(statespace, state, node) + annotation = OverUnderflowAnnotation(state, "add", c) + op0.annotate(annotation) - return issues + def _handle_mul(self, state): + stack = state.mstate.stack + op0, op1 = ( + self._make_bitvec_if_not(stack, -1), + self._make_bitvec_if_not(stack, -2), + ) - def _check_integer_overflow(self, statespace, state, node): - """Checks for integer overflow. + c = Not(BVMulNoOverflow(op0, op1, False)) - :param statespace: statespace that is being examined - :param state: state from node to examine - :param node: node to examine - :return: found issue - """ - issues = [] + # Check satisfiable + model = self._try_constraints(state.node.constraints, [c]) + if model is None: + return - # Check the instruction - instruction = state.get_current_instruction() - if instruction["opcode"] not in ("ADD", "MUL"): - return issues + annotation = OverUnderflowAnnotation(state, "multiply", c) + op0.annotate(annotation) - # Formulate overflow constraints + def _handle_sub(self, state): stack = state.mstate.stack - op0, op1 = stack[-1], stack[-2] - - # An integer overflow is possible if op0 + op1 or op0 * op1 > MAX_UINT - # Do a type check - allowed_types = [int, BitVec] - if not (type(op0) in allowed_types and type(op1) in allowed_types): - return issues - - # Change ints to BitVec - if type(op0) is int: - op0 = symbol_factory.BitVecVal(op0, 256) - if type(op1) is int: - op1 = symbol_factory.BitVecVal(op1, 256) - - # Formulate expression - # FIXME: handle exponentiation - if instruction["opcode"] == "ADD": - operator = "add" - expr = op0 + op1 - constraint = Not(BVAddNoOverflow(op0, op1, signed=False)) - else: - operator = "multiply" - expr = op1 * op0 - constraint = Not(BVMulNoOverflow(op0, op1, signed=False)) + op0, op1 = ( + self._make_bitvec_if_not(stack, -1), + self._make_bitvec_if_not(stack, -2), + ) + c = Not(BVSubNoUnderflow(op0, op1, False)) # Check satisfiable - model = self._try_constraints(node.constraints, [constraint]) - + model = self._try_constraints(state.node.constraints, [c]) if model is None: - log.debug("[INTEGER_OVERFLOW] no model found") - return issues - - # Build issue - issue = Issue( - contract=node.contract_name, - function_name=node.function_name, - address=instruction["address"], - swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, - bytecode=state.environment.code.bytecode, - title="Integer Overflow", - _type="Warning", - gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), - ) - - issue.description = "This binary {} operation can result in integer overflow.\n".format( - operator - ) - try: - issue.debug = str( - solver.get_transaction_sequence(state, node.constraints + [constraint]) - ) - except UnsatError: - return issues - - issues.append(issue) - - return issues - - def _verify_integer_overflow( - self, statespace, node, expr, state, model, constraint, op0, op1 - ): - """Verifies existence of integer overflow. - - :param statespace: - :param node: - :param expr: - :param state: - :param model: - :param constraint: - :param op0: - :param op1: - :return: - """ - # If we get to this point then there has been an integer overflow - # Find out if the overflowed value is actually used - interesting_usages = self._search_children( - statespace, - node, - expr, - constraint=[constraint], - index=node.states.index(state), - ) - - # Stop if it isn't - if len(interesting_usages) == 0: - return False + return - return self._try_constraints(node.constraints, [Not(constraint)]) is not None + annotation = OverUnderflowAnnotation(state, "subtraction", c) + op0.annotate(annotation) @staticmethod - def _try_constraints(constraints, new_constraints): - """Tries new constraints. - - :return Model if satisfiable otherwise None - """ - try: - model = solver.get_model(constraints + new_constraints) - return model - except UnsatError: - return None - - def _check_integer_underflow(self, statespace, state, node): - """Checks for integer underflow. - - :param statespace: - :param state: state from node to examine - :param node: node to examine - :return: found issue - """ - issues = [] - instruction = state.get_current_instruction() - if instruction["opcode"] == "SUB": - - stack = state.mstate.stack - - op0 = stack[-1] - op1 = stack[-2] + def _make_bitvec_if_not(stack, index): + value = stack[index] + if isinstance(value, BitVec): + return value + stack[index] = symbol_factory.BitVecVal(value, 256) + return stack[index] + + def _handle_sstore(self, state): + stack = state.mstate.stack + value = stack[-2] - constraints = copy.deepcopy(node.constraints) + if not isinstance(value, Expression): + return - if type(op0) == int and type(op1) == int: - return [] + for annotation in value.annotations: + if not isinstance(annotation, OverUnderflowAnnotation): + continue - log.debug( - "[INTEGER_UNDERFLOW] Checking SUB {0}, {1} at address {2}".format( - str(op0), str(op1), str(instruction["address"]) - ) + title = ( + "Integer Underflow" + if annotation.operator == "subtraction" + else "Integer Overflow" + ) + ostate = annotation.overflowing_state + node = ostate.node + issue = Issue( + contract=node.contract_name, + function_name=node.function_name, + address=ostate.get_current_instruction()["address"], + swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, + bytecode=ostate.environment.code.bytecode, + title=title, + _type="Warning", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) - allowed_types = [int, BitVec] - - if type(op0) in allowed_types and type(op1) in allowed_types: - constraints.append(Not(BVSubNoUnderflow(op0, op1, signed=False))) - try: - - model = solver.get_model(constraints) - - # If we get to this point then there has been an integer overflow - # Find out if the overflowed value is actually used - interesting_usages = self._search_children( - statespace, node, (op0 - op1), index=node.states.index(state) - ) - - # Stop if it isn't - if len(interesting_usages) == 0: - return issues - - issue = Issue( - contract=node.contract_name, - function_name=node.function_name, - address=instruction["address"], - swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, - bytecode=state.environment.code.bytecode, - title="Integer Underflow", - _type="Warning", - gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), + issue.description = "This binary {} operation can result in {}.\n".format( + annotation.operator, title.lower() + ) + try: + issue.debug = str( + solver.get_transaction_sequence( + state, node.constraints + [annotation.constraint] ) + ) + except UnsatError: + return + self._issues.append(issue) - issue.description = ( - "The subtraction can result in an integer underflow.\n" - ) + def _handle_jumpi(self, state): + stack = state.mstate.stack + value = stack[-2] + + for annotation in value.annotations: + if not isinstance(annotation, OverUnderflowAnnotation): + continue + ostate = annotation.overflowing_state + node = ostate.node + issue = Issue( + contract=node.contract_name, + function_name=node.function_name, + address=ostate.get_current_instruction()["address"], + swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, + bytecode=ostate.environment.code.bytecode, + title="Integer Overflow", + _type="Warning", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), + ) - issue.debug = str( - solver.get_transaction_sequence(state, node.constraints) + issue.description = "This binary {} operation can result in integer overflow.\n".format( + annotation.operator + ) + try: + issue.debug = str( + solver.get_transaction_sequence( + state, node.constraints + [annotation.constraint] ) - issues.append(issue) - - except UnsatError: - log.debug("[INTEGER_UNDERFLOW] no model found") - return issues - - def _check_usage(self, state, taint_result): - """Delegates checks to _check_{instruction_name}() - - :param state: - :param taint_result: - :return: - """ - opcode = state.get_current_instruction()["opcode"] - - if opcode == "JUMPI": - if self._check_jumpi(state, taint_result): - return [state] - elif opcode == "SSTORE": - if self._check_sstore(state, taint_result): - return [state] - return [] - - @staticmethod - def _check_jumpi(state, taint_result): - """Check if conditional jump is dependent on the result of expression. - - :param state: - :param taint_result: - :return: - """ - assert state.get_current_instruction()["opcode"] == "JUMPI" - return taint_result.check(state, -2) + ) + except UnsatError: + return + self._issues.append(issue) @staticmethod - def _check_sstore(state, taint_result): - """Check if store operation is dependent on the result of expression. + def _try_constraints(constraints, new_constraints): + """Tries new constraints. - :param state: - :param taint_result: - :return: - """ - assert state.get_current_instruction()["opcode"] == "SSTORE" - return taint_result.check(state, -2) - - def _search_children( - self, - statespace, - node, - expression, - taint_result=None, - constraint=None, - index=0, - depth=0, - max_depth=64, - ): - """Checks the statespace for children states, with JUMPI or SSTORE - instuctions, for dependency on expression. - - :param statespace: The statespace to explore - :param node: Current node to explore from - :param expression: Expression to look for - :param taint_result: Result of taint analysis - :param constraint: - :param index: Current state index node.states[index] == current_state - :param depth: Current depth level - :param max_depth: Max depth to explore - :return: List of states that match the opcodes and are dependent on expression + :return Model if satisfiable otherwise None """ - if constraint is None: - constraint = [] - - log.debug("SEARCHING NODE for usage of an overflowed variable %d", node.uid) - - if taint_result is None: - state = node.states[index] - taint_stack = [False for _ in state.mstate.stack] - taint_stack[-1] = True - taint_result = TaintRunner.execute( - statespace, node, state, initial_stack=taint_stack - ) - - results = [] - - if depth >= max_depth: - return [] - - # Explore current node from index - for j in range(index, len(node.states)): - current_state = node.states[j] - current_instruction = current_state.get_current_instruction() - if current_instruction["opcode"] in ("JUMPI", "SSTORE"): - element = self._check_usage(current_state, taint_result) - if len(element) < 1: - continue - results += element - - # Recursively search children - children = [ - statespace.nodes[edge.node_to] - for edge in statespace.edges - if edge.node_from == node.uid - # and _try_constraints(statespace.nodes[edge.node_to].constraints, constraint) is not None - ] - - for child in children: - results += self._search_children( - statespace, - child, - expression, - taint_result, - depth=depth + 1, - max_depth=max_depth, - ) - - return results + try: + return solver.get_model(constraints + new_constraints) + except UnsatError: + return None detector = IntegerOverflowUnderflowModule() diff --git a/mythril/analysis/modules/multiple_sends.py b/mythril/analysis/modules/multiple_sends.py index 61ed5a51..1a47c377 100644 --- a/mythril/analysis/modules/multiple_sends.py +++ b/mythril/analysis/modules/multiple_sends.py @@ -1,9 +1,26 @@ """This module contains the detection code to find multiple sends occurring in a single transaction.""" +from copy import copy + from mythril.analysis.report import Issue from mythril.analysis.swc_data import MULTIPLE_SENDS from mythril.analysis.modules.base import DetectionModule -from mythril.laser.ethereum.cfg import JumpType +from mythril.laser.ethereum.state.annotation import StateAnnotation +from mythril.laser.ethereum.state.global_state import GlobalState +import logging +from mythril.analysis.call_helpers import get_call_from_state + +log = logging.getLogger(__name__) + + +class MultipleSendsAnnotation(StateAnnotation): + def __init__(self): + self.calls = [] + + def __copy__(self): + result = MultipleSendsAnnotation() + result.calls = copy(self.calls) + return result class MultipleSendsModule(DetectionModule): @@ -14,103 +31,77 @@ class MultipleSendsModule(DetectionModule): super().__init__( name="Multiple Sends", swc_id=MULTIPLE_SENDS, - hooks=["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"], description="Check for multiple sends in a single transaction", + entrypoint="callback", + pre_hooks=[ + "CALL", + "DELEGATECALL", + "STATICCALL", + "CALLCODE", + "RETURN", + "STOP", + ], ) + self._issues = [] - def execute(self, statespace): - """ - - :param statespace: - :return: - """ - issues = [] - - for call in statespace.calls: - findings = [] - # explore state - findings += self._explore_states(call, statespace) - # explore nodes - findings += self._explore_nodes(call, statespace) - - if len(findings) > 0: - node = call.node - instruction = call.state.get_current_instruction() - issue = Issue( - contract=node.contract_name, - function_name=node.function_name, - address=instruction["address"], - swc_id=MULTIPLE_SENDS, - bytecode=call.state.environment.code.bytecode, - title="Multiple Calls", - _type="Informational", - gas_used=( - call.state.mstate.min_gas_used, - call.state.mstate.max_gas_used, - ), - ) - - issue.description = ( - "Multiple sends are executed in a single transaction. " - "Try to isolate each external call into its own transaction," - " as external calls can fail accidentally or deliberately.\nConsecutive calls: \n" - ) - - for finding in findings: - issue.description += "Call at address: {}\n".format( - finding.state.get_current_instruction()["address"] - ) - - issues.append(issue) - return issues - - def _explore_nodes(self, call, statespace): + def execute(self, state: GlobalState): """ - :param call: - :param statespace: + :param state: :return: """ - children = self._child_nodes(statespace, call.node) - sending_children = list(filter(lambda c: c.node in children, statespace.calls)) - return sending_children - - @staticmethod - def _explore_states(call, statespace): - """ - - :param call: - :param statespace: - :return: - """ - other_calls = list( - filter( - lambda other: other.node == call.node - and other.state_index > call.state_index, - statespace.calls, + self._issues.extend(_analyze_state(state)) + return self.issues + + @property + def issues(self): + return self._issues + + +def _analyze_state(state: GlobalState): + """ + :param state: the current state + :return: returns the issues for that corresponding state + """ + node = state.node + instruction = state.get_current_instruction() + + annotations = [a for a in state.get_annotations(MultipleSendsAnnotation)] + if len(annotations) == 0: + log.debug("Creating annotation for state") + state.annotate(MultipleSendsAnnotation()) + annotations = [a for a in state.get_annotations(MultipleSendsAnnotation)] + + calls = annotations[0].calls + + if instruction["opcode"] in ["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"]: + call = get_call_from_state(state) + if call: + calls += [call] + + else: # RETURN or STOP + if len(calls) > 1: + issue = Issue( + contract=node.contract_name, + function_name=node.function_name, + address=instruction["address"], + swc_id=MULTIPLE_SENDS, + bytecode=state.environment.code.bytecode, + title="Multiple Calls", + _type="Informational", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) - ) - return other_calls - - def _child_nodes(self, statespace, node): - """ - - :param statespace: - :param node: - :return: - """ - result = [] - children = [ - statespace.nodes[edge.node_to] - for edge in statespace.edges - if edge.node_from == node.uid and edge.type != JumpType.Transaction - ] - - for child in children: - result.append(child) - result += self._child_nodes(statespace, child) - - return result + issue.description = ( + "Multiple sends are executed in a single transaction. " + "Try to isolate each external call into its own transaction," + " as external calls can fail accidentally or deliberately.\nConsecutive calls: \n" + ) + for call in calls: + issue.description += "Call at address: {}\n".format( + call.state.get_current_instruction()["address"] + ) + return [issue] + return [] detector = MultipleSendsModule() diff --git a/mythril/analysis/modules/suicide.py b/mythril/analysis/modules/suicide.py index 159fb914..b3bc63c1 100644 --- a/mythril/analysis/modules/suicide.py +++ b/mythril/analysis/modules/suicide.py @@ -1,11 +1,10 @@ -import logging - from mythril.analysis import solver -from mythril.analysis.modules.base import DetectionModule from mythril.analysis.report import Issue from mythril.analysis.swc_data import UNPROTECTED_SELFDESTRUCT from mythril.exceptions import UnsatError +from mythril.analysis.modules.base import DetectionModule from mythril.laser.ethereum.state.global_state import GlobalState +import logging log = logging.getLogger(__name__) @@ -67,9 +66,9 @@ class SuicideModule(DetectionModule): super().__init__( name="Unprotected Suicide", swc_id=UNPROTECTED_SELFDESTRUCT, - hooks=["SUICIDE"], description=DESCRIPTION, entrypoint="callback", + pre_hooks=["SUICIDE"], ) self._issues = [] diff --git a/mythril/analysis/modules/transaction_order_dependence.py b/mythril/analysis/modules/transaction_order_dependence.py index 728bbbe2..3f5eb4e0 100644 --- a/mythril/analysis/modules/transaction_order_dependence.py +++ b/mythril/analysis/modules/transaction_order_dependence.py @@ -21,7 +21,6 @@ class TxOrderDependenceModule(DetectionModule): super().__init__( name="Transaction Order Dependence", swc_id=TX_ORDER_DEPENDENCE, - hooks=[], description=( "This module finds the existance of transaction order dependence " "vulnerabilities. The following webpage contains an extensive description " diff --git a/mythril/analysis/modules/unchecked_retval.py b/mythril/analysis/modules/unchecked_retval.py index 3d89e2fa..2fb36eee 100644 --- a/mythril/analysis/modules/unchecked_retval.py +++ b/mythril/analysis/modules/unchecked_retval.py @@ -1,16 +1,30 @@ """This module contains detection code to find occurrences of calls whose return value remains unchecked.""" +from copy import copy + +from mythril.analysis import solver from mythril.analysis.report import Issue from mythril.analysis.swc_data import UNCHECKED_RET_VAL from mythril.analysis.modules.base import DetectionModule +from mythril.exceptions import UnsatError +from mythril.laser.ethereum.state.annotation import StateAnnotation +from mythril.laser.ethereum.state.global_state import GlobalState -from mythril.laser.ethereum.svm import NodeFlags import logging -import re log = logging.getLogger(__name__) +class UncheckedRetvalAnnotation(StateAnnotation): + def __init__(self): + self.retvals = [] + + def __copy__(self): + result = UncheckedRetvalAnnotation() + result.retvals = copy(self.retvals) + return result + + class UncheckedRetvalModule(DetectionModule): """A detection module to test whether CALL return value is checked.""" @@ -18,7 +32,6 @@ class UncheckedRetvalModule(DetectionModule): super().__init__( name="Unchecked Return Value", swc_id=UNCHECKED_RET_VAL, - hooks=[], description=( "Test whether CALL return value is checked. " "For direct calls, the Solidity compiler auto-generates this check. E.g.:\n" @@ -28,110 +41,71 @@ class UncheckedRetvalModule(DetectionModule): "For low-level-calls this check is omitted. E.g.:\n" ' c.call.value(0)(bytes4(sha3("ping(uint256)")),1);' ), + entrypoint="callback", + pre_hooks=["STOP", "RETURN"], + post_hooks=["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"], ) + self._issues = [] - def execute(self, statespace): + def execute(self, state: GlobalState) -> list: """ - :param statespace: + :param state: :return: """ - log.debug("Executing module: UNCHECKED_RETVAL") - - issues = [] - - for k in statespace.nodes: - - node = statespace.nodes[k] - - if NodeFlags.CALL_RETURN in node.flags: - - retval_checked = False - - for state in node.states: - - instr = state.get_current_instruction() - - if instr["opcode"] == "ISZERO" and re.search( - r"retval", str(state.mstate.stack[-1]) - ): - retval_checked = True - break + self._issues.extend(_analyze_state(state)) + return self.issues - if not retval_checked: + @property + def issues(self): + return self._issues - address = state.get_current_instruction()["address"] - issue = Issue( - contract=node.contract_name, - function_name=node.function_name, - address=address, - bytecode=state.environment.code.bytecode, - title="Unchecked CALL return value", - swc_id=UNCHECKED_RET_VAL, - gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), - ) - issue.description = ( - "The return value of an external call is not checked. " - "Note that execution continue even if the called contract throws." - ) +def _analyze_state(state: GlobalState) -> list: + instruction = state.get_current_instruction() + node = state.node - issues.append(issue) + annotations = [a for a in state.get_annotations(UncheckedRetvalAnnotation)] + if len(annotations) == 0: + state.annotate(UncheckedRetvalAnnotation()) + annotations = [a for a in state.get_annotations(UncheckedRetvalAnnotation)] - else: + retvals = annotations[0].retvals - n_states = len(node.states) - - for idx in range( - 0, n_states - 1 - ): # Ignore CALLs at last position in a node - - state = node.states[idx] - instr = state.get_current_instruction() - - if instr["opcode"] == "CALL": - - retval_checked = False - - for _idx in range(idx, idx + 10): - - try: - _state = node.states[_idx] - _instr = _state.get_current_instruction() - - if _instr["opcode"] == "ISZERO" and re.search( - r"retval", str(_state.mstate.stack[-1]) - ): - retval_checked = True - break - - except IndexError: - break - - if not retval_checked: - - address = instr["address"] - issue = Issue( - contract=node.contract_name, - function_name=node.function_name, - bytecode=state.environment.code.bytecode, - address=address, - title="Unchecked CALL return value", - swc_id=UNCHECKED_RET_VAL, - gas_used=( - state.mstate.min_gas_used, - state.mstate.max_gas_used, - ), - ) - - issue.description = ( - "The return value of an external call is not checked. " - "Note that execution continue even if the called contract throws." - ) - - issues.append(issue) + if instruction["opcode"] in ("STOP", "RETURN"): + issues = [] + for retval in retvals: + try: + model = solver.get_model(node.constraints + [retval["retval"] == 0]) + except UnsatError: + continue + + issue = Issue( + contract=node.contract_name, + function_name=node.function_name, + address=retval["address"], + bytecode=state.environment.code.bytecode, + title="Unchecked CALL return value", + swc_id=UNCHECKED_RET_VAL, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), + ) + + issue.description = ( + "The return value of an external call is not checked. " + "Note that execution continue even if the called contract throws." + ) + issues.append(issue) return issues + else: + log.debug("End of call, extracting retval") + assert state.environment.code.instruction_list[state.mstate.pc - 1][ + "opcode" + ] in ["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"] + retval = state.mstate.stack[-1] + retvals.append({"address": state.instruction["address"] - 1, "retval": retval}) + + return [] detector = UncheckedRetvalModule() diff --git a/mythril/analysis/security.py b/mythril/analysis/security.py index 66bc2ead..81bb69f0 100644 --- a/mythril/analysis/security.py +++ b/mythril/analysis/security.py @@ -1,13 +1,11 @@ """This module contains functionality for hooking in detection modules and executing them.""" -import importlib.util -import logging -import pkgutil from collections import defaultdict - from ethereum.opcodes import opcodes - from mythril.analysis import modules +import pkgutil +import importlib.util +import logging log = logging.getLogger(__name__) @@ -21,16 +19,16 @@ def reset_callback_modules(): module.detector._issues = [] -def get_detection_module_hooks(modules): - """ - - :param modules: - :return: - """ +def get_detection_module_hooks(modules, hook_type="pre"): hook_dict = defaultdict(list) _modules = get_detection_modules(entrypoint="callback", include_modules=modules) for module in _modules: - for op_code in map(lambda x: x.upper(), module.detector.hooks): + hooks = ( + module.detector.pre_hooks + if hook_type == "pre" + else module.detector.post_hooks + ) + for op_code in map(lambda x: x.upper(), hooks): if op_code in OPCODE_LIST: hook_dict[op_code].append(module.detector.execute) elif op_code.endswith("*"): diff --git a/mythril/analysis/solver.py b/mythril/analysis/solver.py index 37969e1a..eed24065 100644 --- a/mythril/analysis/solver.py +++ b/mythril/analysis/solver.py @@ -1,5 +1,7 @@ """This module contains analysis module helpers to solve path constraints.""" from z3 import sat, unknown, FuncInterp +import z3 + from mythril.laser.smt import simplify, UGE, Optimize, symbol_factory from mythril.exceptions import UnsatError from mythril.laser.ethereum.transaction.transaction_models import ( @@ -59,7 +61,7 @@ def pretty_print_model(model): try: condition = "0x%x" % model[d].as_long() except: - condition = str(simplify(model[d])) + condition = str(z3.simplify(model[d])) ret += "%s: %s\n" % (d.name(), condition) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index dd4a701a..bb7dfd33 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -76,7 +76,12 @@ class SymExecWrapper: transaction_count=transaction_count, ) self.laser.register_hooks( - hook_type="pre", hook_dict=get_detection_module_hooks(modules) + hook_type="pre", + hook_dict=get_detection_module_hooks(modules, hook_type="pre"), + ) + self.laser.register_hooks( + hook_type="post", + hook_dict=get_detection_module_hooks(modules, hook_type="post"), ) if isinstance(contract, SolidityContract): diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index b01ab4d9..5d84f781 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -3,19 +3,22 @@ instructions.py to get the necessary elements from the stack and determine the parameters for the new global state.""" import logging -import re -from typing import Union +from typing import Union, List +from mythril.laser.ethereum import natives +from mythril.laser.ethereum.gas import OPCODE_GAS +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, - ConcreteCalldata, SymbolicCalldata, + ConcreteCalldata, + BaseCalldata, ) from mythril.laser.ethereum.state.global_state import GlobalState -from mythril.laser.smt import Expression, simplify, symbol_factory from mythril.support.loader import DynLoader +import re """ This module contains the business logic used by Instruction in instructions.py @@ -194,3 +197,47 @@ def get_call_data( call_data = SymbolicCalldata("{}_internalcall".format(transaction_id)) return call_data, call_data_type + + +def native_call( + global_state: GlobalState, + callee_address: str, + call_data: BaseCalldata, + memory_out_offset: Union[int, Expression], + memory_out_size: Union[int, Expression], +) -> Union[List[GlobalState], None]: + if not 0 < int(callee_address, 16) < 5: + return None + + log.debug("Native contract called: " + callee_address) + try: + mem_out_start = util.get_concrete_int(memory_out_offset) + mem_out_sz = util.get_concrete_int(memory_out_size) + except TypeError: + log.debug("CALL with symbolic start or offset not supported") + return [global_state] + + contract_list = ["ecrecover", "sha256", "ripemd160", "identity"] + call_address_int = int(callee_address, 16) + native_gas_min, native_gas_max = OPCODE_GAS["NATIVE_COST"]( + global_state.mstate.calculate_extension_size(mem_out_start, mem_out_sz), + contract_list[call_address_int - 1], + ) + global_state.mstate.min_gas_used += native_gas_min + global_state.mstate.max_gas_used += native_gas_max + global_state.mstate.mem_extend(mem_out_start, mem_out_sz) + try: + data = natives.native_contracts(call_address_int, call_data) + except natives.NativeContractException: + for i in range(mem_out_sz): + global_state.mstate.memory[mem_out_start + i] = global_state.new_bitvec( + contract_list[call_address_int - 1] + "(" + str(call_data) + ")", 8 + ) + return [global_state] + + for i in range( + min(len(data), mem_out_sz) + ): # If more data is used then it's chopped off + global_state.mstate.memory[mem_out_start + i] = data[i] + # TODO: maybe use BitVec here constrained to 1 + return [global_state] diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 8d250dee..d6abcd9c 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -2,31 +2,12 @@ transitions between them.""" import binascii import logging + from copy import copy, deepcopy from typing import Callable, List, Union from ethereum import utils -import mythril.laser.ethereum.natives as natives -import mythril.laser.ethereum.util as helper -from mythril.laser.ethereum import util -from mythril.laser.ethereum.call import get_call_parameters -from mythril.laser.ethereum.evm_exceptions import ( - VmException, - StackUnderflowException, - InvalidJumpDestination, - InvalidInstruction, - OutOfGasException, -) -from mythril.laser.ethereum.gas import OPCODE_GAS -from mythril.laser.ethereum.keccak import KeccakFunctionManager -from mythril.laser.ethereum.state.calldata import CalldataType -from mythril.laser.ethereum.state.global_state import GlobalState -from mythril.laser.ethereum.transaction import ( - MessageCallTransaction, - TransactionStartSignal, - ContractCreationTransaction, -) from mythril.laser.smt import ( Extract, Expression, @@ -46,6 +27,26 @@ from mythril.laser.smt import ( Not, ) from mythril.laser.smt import symbol_factory + +import mythril.laser.ethereum.util as helper +from mythril.laser.ethereum import util +from mythril.laser.ethereum.call import get_call_parameters, native_call +from mythril.laser.ethereum.evm_exceptions import ( + VmException, + StackUnderflowException, + InvalidJumpDestination, + InvalidInstruction, + OutOfGasException, +) +from mythril.laser.ethereum.gas import OPCODE_GAS +from mythril.laser.ethereum.keccak import KeccakFunctionManager +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.laser.ethereum.transaction import ( + MessageCallTransaction, + TransactionStartSignal, + ContractCreationTransaction, +) + from mythril.support.loader import DynLoader log = logging.getLogger(__name__) @@ -634,8 +635,12 @@ class Instruction: state = global_state.mstate val = state.stack.pop() - exp = (val == False) if isinstance(val, Bool) else val == 0 - state.stack.append(exp) + exp = Not(val) if isinstance(val, Bool) else val == 0 + + exp = If( + exp, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256) + ) + state.stack.append(simplify(exp)) return [global_state] @@ -1327,7 +1332,7 @@ class Instruction: keccak_keys = filter(keccak_function_manager.is_keccak, storage_keys) results = [] - new = False + new = symbol_factory.Bool(False) for keccak_key in keccak_keys: key_argument = keccak_function_manager.get_argument(keccak_key) @@ -1458,9 +1463,9 @@ class Instruction: negated = ( simplify(Not(condition)) if isinstance(condition, Bool) else condition == 0 ) - + negated.simplify() if (type(negated) == bool and negated) or ( - isinstance(condition, Bool) and not is_false(negated) + isinstance(negated, Bool) and not is_false(negated) ): new_state = copy(global_state) # add JUMPI gas cost @@ -1486,6 +1491,7 @@ class Instruction: instr = disassembly.instruction_list[index] condi = simplify(condition) if isinstance(condition, Bool) else condition != 0 + condi.simplify() if instr["opcode"] == "JUMPDEST": if (type(condi) == bool and condi) or ( isinstance(condi, Bool) and not is_false(condi) @@ -1690,49 +1696,11 @@ class Instruction: global_state.new_bitvec("retval_" + str(instr["address"]), 256) ) - if 0 < int(callee_address, 16) < 5: - log.debug("Native contract called: " + callee_address) - if call_data == [] and call_data_type == CalldataType.SYMBOLIC: - log.debug("CALL with symbolic data not supported") - return [global_state] - - try: - mem_out_start = helper.get_concrete_int(memory_out_offset) - mem_out_sz = helper.get_concrete_int(memory_out_size) - except TypeError: - log.debug("CALL with symbolic start or offset not supported") - return [global_state] - - contract_list = ["ecrecover", "sha256", "ripemd160", "identity"] - call_address_int = int(callee_address, 16) - native_gas_min, native_gas_max = OPCODE_GAS["NATIVE_COST"]( - global_state.mstate.calculate_extension_size(mem_out_start, mem_out_sz), - contract_list[call_address_int - 1], - ) - global_state.mstate.min_gas_used += native_gas_min - global_state.mstate.max_gas_used += native_gas_max - global_state.mstate.mem_extend(mem_out_start, mem_out_sz) - try: - data = natives.native_contracts(call_address_int, call_data) - except natives.NativeContractException: - for i in range(mem_out_sz): - global_state.mstate.memory[ - mem_out_start + i - ] = global_state.new_bitvec( - contract_list[call_address_int - 1] - + "(" - + str(call_data) - + ")", - 8, - ) - return [global_state] - - for i in range( - min(len(data), mem_out_sz) - ): # If more data is used then it's chopped off - global_state.mstate.memory[mem_out_start + i] = data[i] - # TODO: maybe use BitVec here constrained to 1 - return [global_state] + native_result = native_call( + global_state, callee_address, call_data, memory_out_offset, memory_out_size + ) + if native_result: + return native_result transaction = MessageCallTransaction( world_state=global_state.world_state, @@ -2033,7 +2001,28 @@ 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( + global_state, self.dynamic_loader + ) + except ValueError as e: + log.debug( + "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format( + e + ) + ) + global_state.mstate.stack.append( + global_state.new_bitvec("retval_" + str(instr["address"]), 256) + ) + return [global_state] + global_state.mstate.stack.append( global_state.new_bitvec("retval_" + str(instr["address"]), 256) ) + native_result = native_call( + global_state, callee_address, call_data, memory_out_offset, memory_out_size + ) + if native_result: + return native_result + return [global_state] diff --git a/mythril/laser/ethereum/state/account.py b/mythril/laser/ethereum/state/account.py index a5fd1d2d..3d13428f 100644 --- a/mythril/laser/ethereum/state/account.py +++ b/mythril/laser/ethereum/state/account.py @@ -34,11 +34,14 @@ class Storage: and (self.dynld and self.dynld.storage_loading) ): try: - self._storage[item] = int( - self.dynld.read_storage( - contract_address=self.address, index=int(item) + self._storage[item] = symbol_factory.BitVecVal( + int( + self.dynld.read_storage( + contract_address=self.address, index=int(item) + ), + 16, ), - 16, + 256, ) return self._storage[item] except ValueError: diff --git a/mythril/laser/ethereum/state/machine_state.py b/mythril/laser/ethereum/state/machine_state.py index b4279c90..45e824db 100644 --- a/mythril/laser/ethereum/state/machine_state.py +++ b/mythril/laser/ethereum/state/machine_state.py @@ -82,8 +82,8 @@ class MachineStack(list): class MachineState: - """MachineState represents current machine state also referenced to as - \mu.""" + """MachineState represents current machine state also referenced to as \mu. + """ def __init__( self, diff --git a/mythril/laser/smt/__init__.py b/mythril/laser/smt/__init__.py index 85c72e60..ab79a211 100644 --- a/mythril/laser/smt/__init__.py +++ b/mythril/laser/smt/__init__.py @@ -26,6 +26,16 @@ class SymbolFactory: """A symbol factory provides a default interface for all the components of mythril to create symbols.""" + @staticmethod + def Bool(value: bool, annotations=None): + """Creates a Bool with concrete value. + + :param value: The boolean value + :param annotations: The annotations to initialize the bool with + :return: The freshly created Bool() + """ + raise NotImplementedError + @staticmethod def BitVecVal(value: int, size: int, annotations=None): """Creates a new bit vector with a concrete value. @@ -53,6 +63,17 @@ class _SmtSymbolFactory(SymbolFactory): """An implementation of a SymbolFactory that creates symbols using the classes in: mythril.laser.smt.""" + @staticmethod + def Bool(value: bool, annotations=None): + """Creates a Bool with concrete value. + + :param value: The boolean value + :param annotations: The annotations to initialize the bool with + :return: The freshly created Bool() + """ + raw = z3.Bool(value) + return Bool(raw, annotations) + @staticmethod def BitVecVal(value: int, size: int, annotations=None): """Creates a new bit vector with a concrete value.""" @@ -70,6 +91,11 @@ class _Z3SymbolFactory(SymbolFactory): """An implementation of a SymbolFactory that directly returns z3 symbols.""" + @staticmethod + def Bool(value: bool, annotations=None): + """Creates a new bit vector with a concrete value.""" + return z3.Bool(value) + @staticmethod def BitVecVal(value: int, size: int, annotations=None): """Creates a new bit vector with a concrete value.""" diff --git a/tests/graph_test.py b/tests/graph_test.py index 1fa342d7..ba84f7da 100644 --- a/tests/graph_test.py +++ b/tests/graph_test.py @@ -28,6 +28,7 @@ class GraphTest(BaseTestCase): address=(util.get_indexed_address(0)), strategy="dfs", transaction_count=1, + execution_timeout=5, ) html = generate_graph(sym) diff --git a/tests/native_test.py b/tests/native_test.py index 1f25741f..fdfb392a 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -14,12 +14,12 @@ IDENTITY_TEST = [(0, False) for _ in range(2)] # These are Random numbers to check whether the 'if condition' is entered or not # (True means entered) SHA256_TEST[0] = (hex(5555555555555555), True) -SHA256_TEST[1] = (hex(323232325445454546), True) +SHA256_TEST[1] = (hex(323232325445454546), False) SHA256_TEST[2] = (hex(34756834765834658), True) -SHA256_TEST[3] = (hex(8756476956956795876987), True) +SHA256_TEST[3] = (hex(8756476956956795876987), False) RIPEMD160_TEST[0] = (hex(999999999999999999993), True) -RIPEMD160_TEST[1] = (hex(1111111111112), True) +RIPEMD160_TEST[1] = (hex(1111111111112), False) ECRECOVER_TEST[0] = (hex(674837568743979857398564869), True) ECRECOVER_TEST[1] = (hex(3487683476979311), False) diff --git a/tests/testdata/outputs_expected/calls.sol.o.json b/tests/testdata/outputs_expected/calls.sol.o.json index d8b68faa..404801ff 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.json +++ b/tests/testdata/outputs_expected/calls.sol.o.json @@ -1 +1,102 @@ -{"error": null, "issues": [{"address": 661, "contract": "Unknown", "debug": "", "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", "function": "thisisfine()", "max_gas_used": 1254, "min_gas_used": 643, "swc-id": "107", "title": "External call", "type": "Informational"}, {"address": 666, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "thisisfine()", "max_gas_used": 35963, "min_gas_used": 1352, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 779, "contract": "Unknown", "debug": "", "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", "function": "callstoredaddress()", "max_gas_used": 1298, "min_gas_used": 687, "swc-id": "107", "title": "External call", "type": "Informational"}, {"address": 784, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "callstoredaddress()", "max_gas_used": 36007, "min_gas_used": 1396, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 858, "contract": "Unknown", "debug": "", "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", "function": "reentrancy()", "max_gas_used": 1320, "min_gas_used": 709, "swc-id": "107", "title": "External call", "type": "Informational"}, {"address": 871, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "reentrancy()", "max_gas_used": 61043, "min_gas_used": 6432, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 912, "contract": "Unknown", "debug": "", "description": "The contract executes a function call with high gas to a user-supplied address. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent unanticipated effects on the contract state.", "function": "calluseraddress(address)", "max_gas_used": 616, "min_gas_used": 335, "swc-id": "107", "title": "External call to user-supplied address", "type": "Warning"}, {"address": 918, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "calluseraddress(address)", "max_gas_used": 35327, "min_gas_used": 1046, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 661, + "contract": "Unknown", + "debug": "", + "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", + "function": "thisisfine()", + "max_gas_used": 1254, + "min_gas_used": 643, + "swc-id": "107", + "title": "External call", + "type": "Informational" + }, + { + "address": 661, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "thisisfine()", + "max_gas_used": 35972, + "min_gas_used": 1361, + "swc-id": "104", + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 779, + "contract": "Unknown", + "debug": "", + "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", + "function": "callstoredaddress()", + "max_gas_used": 1298, + "min_gas_used": 687, + "swc-id": "107", + "title": "External call", + "type": "Informational" + }, + { + "address": 779, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "callstoredaddress()", + "max_gas_used": 36016, + "min_gas_used": 1405, + "swc-id": "104", + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 858, + "contract": "Unknown", + "debug": "", + "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", + "function": "reentrancy()", + "max_gas_used": 1320, + "min_gas_used": 709, + "swc-id": "107", + "title": "External call", + "type": "Informational" + }, + { + "address": 858, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "reentrancy()", + "max_gas_used": 61052, + "min_gas_used": 6441, + "swc-id": "104", + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 912, + "contract": "Unknown", + "debug": "", + "description": "The contract executes a function call with high gas to a user-supplied address. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent unanticipated effects on the contract state.", + "function": "calluseraddress(address)", + "max_gas_used": 616, + "min_gas_used": 335, + "swc-id": "107", + "title": "External call to user-supplied address", + "type": "Warning" + }, + { + "address": 912, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "calluseraddress(address)", + "max_gas_used": 35336, + "min_gas_used": 1055, + "swc-id": "104", + "title": "Unchecked CALL return value", + "type": "Informational" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/calls.sol.o.markdown b/tests/testdata/outputs_expected/calls.sol.o.markdown index ff656859..34e48962 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/calls.sol.o.markdown @@ -17,8 +17,8 @@ The contract executes a function call to an external address. Verify that the co - Type: Informational - Contract: Unknown - Function name: `thisisfine()` -- PC address: 666 -- Estimated Gas Usage: 1352 - 35963 +- PC address: 661 +- Estimated Gas Usage: 1361 - 35972 ### Description @@ -41,8 +41,8 @@ The contract executes a function call to an external address. Verify that the co - Type: Informational - Contract: Unknown - Function name: `callstoredaddress()` -- PC address: 784 -- Estimated Gas Usage: 1396 - 36007 +- PC address: 779 +- Estimated Gas Usage: 1405 - 36016 ### Description @@ -65,8 +65,8 @@ The contract executes a function call to an external address. Verify that the co - Type: Informational - Contract: Unknown - Function name: `reentrancy()` -- PC address: 871 -- Estimated Gas Usage: 6432 - 61043 +- PC address: 858 +- Estimated Gas Usage: 6441 - 61052 ### Description @@ -89,8 +89,8 @@ The contract executes a function call with high gas to a user-supplied address. - Type: Informational - Contract: Unknown - Function name: `calluseraddress(address)` -- PC address: 918 -- Estimated Gas Usage: 1046 - 35327 +- PC address: 912 +- Estimated Gas Usage: 1055 - 35336 ### Description diff --git a/tests/testdata/outputs_expected/calls.sol.o.text b/tests/testdata/outputs_expected/calls.sol.o.text index 1cbfb35e..69dc3d29 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.text +++ b/tests/testdata/outputs_expected/calls.sol.o.text @@ -13,8 +13,8 @@ SWC ID: 104 Type: Informational Contract: Unknown Function name: thisisfine() -PC address: 666 -Estimated Gas Usage: 1352 - 35963 +PC address: 661 +Estimated Gas Usage: 1361 - 35972 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -33,8 +33,8 @@ SWC ID: 104 Type: Informational Contract: Unknown Function name: callstoredaddress() -PC address: 784 -Estimated Gas Usage: 1396 - 36007 +PC address: 779 +Estimated Gas Usage: 1405 - 36016 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -53,8 +53,8 @@ SWC ID: 104 Type: Informational Contract: Unknown Function name: reentrancy() -PC address: 871 -Estimated Gas Usage: 6432 - 61043 +PC address: 858 +Estimated Gas Usage: 6441 - 61052 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -73,8 +73,8 @@ SWC ID: 104 Type: Informational Contract: Unknown Function name: calluseraddress(address) -PC address: 918 -Estimated Gas Usage: 1046 - 35327 +PC address: 912 +Estimated Gas Usage: 1055 - 35336 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json index 1ce4f764..26366268 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json @@ -1,6 +1,18 @@ { "error": null, "issues": [ + { + "address": 618, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0x141f32ff", + "max_gas_used": 35865, + "min_gas_used": 1113, + "swc-id": "104", + "title": "Unchecked CALL return value", + "type": "Informational" + }, { "address": 618, "contract": "Unknown", @@ -14,25 +26,13 @@ "type": "Warning" }, { - "address": 626, - "contract": "Unknown", - "debug": "", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0x141f32ff", - "max_gas_used": 35856, - "min_gas_used": 1104, - "swc-id": "104", - "title": "Unchecked CALL return value", - "type": "Informational" - }, - { - "address": 857, + "address": 849, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x9b58bc26", - "max_gas_used": 35913, - "min_gas_used": 1161, + "max_gas_used": 35922, + "min_gas_used": 1170, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational" @@ -50,13 +50,13 @@ "type": "Warning" }, { - "address": 1046, + "address": 1038, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xeea4c864", - "max_gas_used": 35938, - "min_gas_used": 1186, + "max_gas_used": 35947, + "min_gas_used": 1195, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational" diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown index 331301d8..2b16432e 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown @@ -1,36 +1,36 @@ # Analysis results for test-filename.sol -## Use of callcode -- SWC ID: 111 -- Type: Warning +## Unchecked CALL return value +- SWC ID: 104 +- Type: Informational - Contract: Unknown - Function name: `_function_0x141f32ff` - PC address: 618 -- Estimated Gas Usage: 389 - 1141 +- Estimated Gas Usage: 1113 - 35865 ### Description -The function `_function_0x141f32ff` uses callcode. Callcode does not persist sender or value over the call. Use delegatecall instead. +The return value of an external call is not checked. Note that execution continue even if the called contract throws. -## Unchecked CALL return value -- SWC ID: 104 -- Type: Informational +## Use of callcode +- SWC ID: 111 +- Type: Warning - Contract: Unknown - Function name: `_function_0x141f32ff` -- PC address: 626 -- Estimated Gas Usage: 1104 - 35856 +- PC address: 618 +- Estimated Gas Usage: 389 - 1141 ### Description -The return value of an external call is not checked. Note that execution continue even if the called contract throws. +The function `_function_0x141f32ff` uses callcode. Callcode does not persist sender or value over the call. Use delegatecall instead. ## Unchecked CALL return value - SWC ID: 104 - Type: Informational - Contract: Unknown - Function name: `_function_0x9b58bc26` -- PC address: 857 -- Estimated Gas Usage: 1161 - 35913 +- PC address: 849 +- Estimated Gas Usage: 1170 - 35922 ### Description @@ -53,8 +53,8 @@ The contract executes a function call with high gas to a user-supplied address. - Type: Informational - Contract: Unknown - Function name: `_function_0xeea4c864` -- PC address: 1046 -- Estimated Gas Usage: 1186 - 35938 +- PC address: 1038 +- Estimated Gas Usage: 1195 - 35947 ### Description diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text index bb64d557..b4bba9ca 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text @@ -1,3 +1,13 @@ +==== Unchecked CALL return value ==== +SWC ID: 104 +Type: Informational +Contract: Unknown +Function name: _function_0x141f32ff +PC address: 618 +Estimated Gas Usage: 1113 - 35865 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + ==== Use of callcode ==== SWC ID: 111 Type: Warning @@ -8,23 +18,13 @@ Estimated Gas Usage: 389 - 1141 The function `_function_0x141f32ff` uses callcode. Callcode does not persist sender or value over the call. Use delegatecall instead. -------------------- -==== Unchecked CALL return value ==== -SWC ID: 104 -Type: Informational -Contract: Unknown -Function name: _function_0x141f32ff -PC address: 626 -Estimated Gas Usage: 1104 - 35856 -The return value of an external call is not checked. Note that execution continue even if the called contract throws. --------------------- - ==== Unchecked CALL return value ==== SWC ID: 104 Type: Informational Contract: Unknown Function name: _function_0x9b58bc26 -PC address: 857 -Estimated Gas Usage: 1161 - 35913 +PC address: 849 +Estimated Gas Usage: 1170 - 35922 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -43,8 +43,8 @@ SWC ID: 104 Type: Informational Contract: Unknown Function name: _function_0xeea4c864 -PC address: 1046 -Estimated Gas Usage: 1186 - 35938 +PC address: 1038 +Estimated Gas Usage: 1195 - 35947 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json index 17b44a75..aefffb2b 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ b/tests/testdata/outputs_expected/overflow.sol.o.json @@ -1,30 +1,42 @@ { - "error": null, - "issues": [ - { - "address": 567, - "contract": "Unknown", - "debug": "", - "description": "The subtraction can result in an integer underflow.\n", - "function": "sendeth(address,uint256)", - "max_gas_used": 1035, - "min_gas_used": 750, - "swc-id": "101", - "title": "Integer Underflow", - "type": "Warning" - }, - { - "address": 649, - "contract": "Unknown", - "debug": "", - "description": "The subtraction can result in an integer underflow.\n", - "function": "sendeth(address,uint256)", - "max_gas_used": 1758, - "min_gas_used": 1283, - "swc-id": "101", - "title": "Integer Underflow", - "type": "Warning" - } - ], - "success": true + "error":null, + "issues":[ + { + "address":567, + "contract":"Unknown", + "debug":"", + "description":"This binary subtraction operation can result in integer overflow.\n", + "function":"sendeth(address,uint256)", + "max_gas_used":1053, + "min_gas_used":768, + "swc-id":"101", + "title":"Integer Overflow", + "type":"Warning" + }, + { + "address":567, + "contract":"Unknown", + "debug":"", + "description":"This binary subtraction operation can result in integer underflow.\n", + "function":"sendeth(address,uint256)", + "max_gas_used":1774, + "min_gas_used":1299, + "swc-id":"101", + "title":"Integer Underflow", + "type":"Warning" + }, + { + "address":649, + "contract":"Unknown", + "debug":"", + "description":"This binary subtraction operation can result in integer underflow.\n", + "function":"sendeth(address,uint256)", + "max_gas_used":1774, + "min_gas_used":1299, + "swc-id":"101", + "title":"Integer Underflow", + "type":"Warning" + } + ], + "success":true } diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown index 0e50eb96..ce87c901 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.o.markdown @@ -1,16 +1,28 @@ # Analysis results for test-filename.sol +## Integer Overflow +- SWC ID: 101 +- Type: Warning +- Contract: Unknown +- Function name: `sendeth(address,uint256)` +- PC address: 567 +- Estimated Gas Usage: 768 - 1053 + +### Description + +This binary subtraction operation can result in integer overflow. + ## Integer Underflow - SWC ID: 101 - Type: Warning - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 567 -- Estimated Gas Usage: 750 - 1035 +- Estimated Gas Usage: 1299 - 1774 ### Description -The subtraction can result in an integer underflow. +This binary subtraction operation can result in integer underflow. ## Integer Underflow - SWC ID: 101 @@ -18,8 +30,8 @@ The subtraction can result in an integer underflow. - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 649 -- Estimated Gas Usage: 1283 - 1758 +- Estimated Gas Usage: 1299 - 1774 ### Description -The subtraction can result in an integer underflow. +This binary subtraction operation can result in integer underflow. diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text index 3f13675f..fdea1818 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ b/tests/testdata/outputs_expected/overflow.sol.o.text @@ -1,11 +1,22 @@ +==== Integer Overflow ==== +SWC ID: 101 +Type: Warning +Contract: Unknown +Function name: sendeth(address,uint256) +PC address: 567 +Estimated Gas Usage: 768 - 1053 +This binary subtraction operation can result in integer overflow. + +-------------------- + ==== Integer Underflow ==== SWC ID: 101 Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 567 -Estimated Gas Usage: 750 - 1035 -The subtraction can result in an integer underflow. +Estimated Gas Usage: 1299 - 1774 +This binary subtraction operation can result in integer underflow. -------------------- @@ -15,8 +26,8 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 649 -Estimated Gas Usage: 1283 - 1758 -The subtraction can result in an integer underflow. +Estimated Gas Usage: 1299 - 1774 +This binary subtraction operation can result in integer underflow. -------------------- diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.json b/tests/testdata/outputs_expected/returnvalue.sol.o.json index cdcb199b..17d61c3f 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.json +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.json @@ -1 +1,42 @@ -{"error": null, "issues": [{"address": 196, "contract": "Unknown", "debug": "", "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", "function": "callchecked()", "max_gas_used": 1210, "min_gas_used": 599, "swc-id": "107", "title": "External call", "type": "Informational"}, {"address": 285, "contract": "Unknown", "debug": "", "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", "function": "callnotchecked()", "max_gas_used": 1232, "min_gas_used": 621, "swc-id": "107", "title": "External call", "type": "Informational"}, {"address": 290, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "callnotchecked()", "max_gas_used": 35941, "min_gas_used": 1330, "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 196, + "contract": "Unknown", + "debug": "", + "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", + "function": "callchecked()", + "max_gas_used": 1210, + "min_gas_used": 599, + "swc-id": "107", + "title": "External call", + "type": "Informational" + }, + { + "address": 285, + "contract": "Unknown", + "debug": "", + "description": "The contract executes a function call to an external address. Verify that the code at this address is trusted and immutable.", + "function": "callnotchecked()", + "max_gas_used": 1232, + "min_gas_used": 621, + "swc-id": "107", + "title": "External call", + "type": "Informational" + }, + { + "address": 285, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "callnotchecked()", + "max_gas_used": 35950, + "min_gas_used": 1339, + "swc-id": "104", + "title": "Unchecked CALL return value", + "type": "Informational" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown index d733d759..6b34840f 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown @@ -29,8 +29,8 @@ The contract executes a function call to an external address. Verify that the co - Type: Informational - Contract: Unknown - Function name: `callnotchecked()` -- PC address: 290 -- Estimated Gas Usage: 1330 - 35941 +- PC address: 285 +- Estimated Gas Usage: 1339 - 35950 ### Description diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.text b/tests/testdata/outputs_expected/returnvalue.sol.o.text index 29c4b0d0..320b0420 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.text +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.text @@ -23,8 +23,8 @@ SWC ID: 104 Type: Informational Contract: Unknown Function name: callnotchecked() -PC address: 290 -Estimated Gas Usage: 1330 - 35941 +PC address: 285 +Estimated Gas Usage: 1339 - 35950 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json index 17b44a75..aefffb2b 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ b/tests/testdata/outputs_expected/underflow.sol.o.json @@ -1,30 +1,42 @@ { - "error": null, - "issues": [ - { - "address": 567, - "contract": "Unknown", - "debug": "", - "description": "The subtraction can result in an integer underflow.\n", - "function": "sendeth(address,uint256)", - "max_gas_used": 1035, - "min_gas_used": 750, - "swc-id": "101", - "title": "Integer Underflow", - "type": "Warning" - }, - { - "address": 649, - "contract": "Unknown", - "debug": "", - "description": "The subtraction can result in an integer underflow.\n", - "function": "sendeth(address,uint256)", - "max_gas_used": 1758, - "min_gas_used": 1283, - "swc-id": "101", - "title": "Integer Underflow", - "type": "Warning" - } - ], - "success": true + "error":null, + "issues":[ + { + "address":567, + "contract":"Unknown", + "debug":"", + "description":"This binary subtraction operation can result in integer overflow.\n", + "function":"sendeth(address,uint256)", + "max_gas_used":1053, + "min_gas_used":768, + "swc-id":"101", + "title":"Integer Overflow", + "type":"Warning" + }, + { + "address":567, + "contract":"Unknown", + "debug":"", + "description":"This binary subtraction operation can result in integer underflow.\n", + "function":"sendeth(address,uint256)", + "max_gas_used":1774, + "min_gas_used":1299, + "swc-id":"101", + "title":"Integer Underflow", + "type":"Warning" + }, + { + "address":649, + "contract":"Unknown", + "debug":"", + "description":"This binary subtraction operation can result in integer underflow.\n", + "function":"sendeth(address,uint256)", + "max_gas_used":1774, + "min_gas_used":1299, + "swc-id":"101", + "title":"Integer Underflow", + "type":"Warning" + } + ], + "success":true } diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown index 0e50eb96..ce87c901 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.o.markdown @@ -1,16 +1,28 @@ # Analysis results for test-filename.sol +## Integer Overflow +- SWC ID: 101 +- Type: Warning +- Contract: Unknown +- Function name: `sendeth(address,uint256)` +- PC address: 567 +- Estimated Gas Usage: 768 - 1053 + +### Description + +This binary subtraction operation can result in integer overflow. + ## Integer Underflow - SWC ID: 101 - Type: Warning - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 567 -- Estimated Gas Usage: 750 - 1035 +- Estimated Gas Usage: 1299 - 1774 ### Description -The subtraction can result in an integer underflow. +This binary subtraction operation can result in integer underflow. ## Integer Underflow - SWC ID: 101 @@ -18,8 +30,8 @@ The subtraction can result in an integer underflow. - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 649 -- Estimated Gas Usage: 1283 - 1758 +- Estimated Gas Usage: 1299 - 1774 ### Description -The subtraction can result in an integer underflow. +This binary subtraction operation can result in integer underflow. diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text index 3f13675f..fdea1818 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ b/tests/testdata/outputs_expected/underflow.sol.o.text @@ -1,11 +1,22 @@ +==== Integer Overflow ==== +SWC ID: 101 +Type: Warning +Contract: Unknown +Function name: sendeth(address,uint256) +PC address: 567 +Estimated Gas Usage: 768 - 1053 +This binary subtraction operation can result in integer overflow. + +-------------------- + ==== Integer Underflow ==== SWC ID: 101 Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 567 -Estimated Gas Usage: 750 - 1035 -The subtraction can result in an integer underflow. +Estimated Gas Usage: 1299 - 1774 +This binary subtraction operation can result in integer underflow. -------------------- @@ -15,8 +26,8 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 649 -Estimated Gas Usage: 1283 - 1758 -The subtraction can result in an integer underflow. +Estimated Gas Usage: 1299 - 1774 +This binary subtraction operation can result in integer underflow. --------------------