Source code for mythril.analysis.module.modules.external_calls

"""This module contains the detection code for potentially insecure low-level
calls."""

from mythril.analysis import solver
from mythril.analysis.potential_issues import (
    PotentialIssue,
    get_potential_issues_annotation,
)
from mythril.analysis.swc_data import REENTRANCY
from mythril.laser.ethereum.state.constraints import Constraints
from mythril.laser.ethereum.transaction.symbolic import ACTORS
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.smt import UGT, symbol_factory, Or, BitVec
from mythril.laser.ethereum.natives import PRECOMPILE_COUNT
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.exceptions import UnsatError
from copy import copy
import logging

log = logging.getLogger(__name__)

DESCRIPTION = """

Search for external calls with unrestricted gas to a user-specified address.

"""


def _is_precompile_call(global_state: GlobalState):
    to = global_state.mstate.stack[-2]  # type: BitVec
    constraints = copy(global_state.world_state.constraints)
    constraints += [
        Or(
            to < symbol_factory.BitVecVal(1, 256),
            to > symbol_factory.BitVecVal(PRECOMPILE_COUNT, 256),
        )
    ]

    try:
        solver.get_model(constraints)
        return False
    except UnsatError:
        return True


[docs]class ExternalCalls(DetectionModule): """This module searches for low level calls (e.g. call.value()) that forward all gas to the callee.""" name = "External call to another contract" swc_id = REENTRANCY description = DESCRIPTION entry_point = EntryPoint.CALLBACK pre_hooks = ["CALL"] def _execute(self, state: GlobalState) -> None: """ :param state: :return: """ potential_issues = self._analyze_state(state) annotation = get_potential_issues_annotation(state) annotation.potential_issues.extend(potential_issues) def _analyze_state(self, state: GlobalState): """ :param state: :return: """ gas = state.mstate.stack[-1] to = state.mstate.stack[-2] address = state.get_current_instruction()["address"] try: constraints = Constraints( [UGT(gas, symbol_factory.BitVecVal(2300, 256)), to == ACTORS.attacker] ) solver.get_transaction_sequence( state, constraints + state.world_state.constraints ) description_head = "A call to a user-supplied address is executed." description_tail = ( "An external message call to an address specified by the caller is executed. Note that " "the callee account might contain arbitrary code and could re-enter any function " "within this contract. Reentering the contract in an intermediate state may lead to " "unexpected behaviour. Make sure that no state modifications " "are executed after this call and/or reentrancy guards are in place." ) issue = PotentialIssue( contract=state.environment.active_account.contract_name, function_name=state.environment.active_function_name, address=address, swc_id=REENTRANCY, title="External Call To User-Supplied Address", bytecode=state.environment.code.bytecode, severity="Low", description_head=description_head, description_tail=description_tail, constraints=constraints, detector=self, ) except UnsatError: log.debug("[EXTERNAL_CALLS] No model found.") return [] return [issue]
detector = ExternalCalls()