Пример #1
0
    def _handle_transaction_end(self, state: GlobalState) -> None:

        state_annotation = _get_overflowunderflow_state_annotation(state)

        for annotation in state_annotation.overflowing_state_annotations:

            ostate = annotation.overflowing_state

            if ostate in self._ostates_unsatisfiable:
                continue

            if ostate not in self._ostates_satisfiable:
                try:
                    constraints = ostate.mstate.constraints + [
                        annotation.constraint
                    ]
                    solver.get_model(constraints)
                    self._ostates_satisfiable.add(ostate)
                except:
                    self._ostates_unsatisfiable.add(ostate)
                    continue

            log.debug(
                "Checking overflow in {} at transaction end address {}, ostate address {}"
                .format(
                    state.get_current_instruction()["opcode"],
                    state.get_current_instruction()["address"],
                    ostate.get_current_instruction()["address"],
                ))

            try:

                constraints = state.mstate.constraints + [
                    annotation.constraint
                ]
                transaction_sequence = solver.get_transaction_sequence(
                    state, constraints)
            except UnsatError:
                continue

            _type = "Underflow" if annotation.operator == "subtraction" else "Overflow"
            issue = Issue(
                contract=ostate.environment.active_account.contract_name,
                function_name=ostate.environment.active_function_name,
                address=ostate.get_current_instruction()["address"],
                swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                bytecode=ostate.environment.code.bytecode,
                title=self._get_title(_type),
                severity="High",
                description_head=self._get_description_head(annotation, _type),
                description_tail=self._get_description_tail(annotation, _type),
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
                transaction_sequence=transaction_sequence,
            )

            address = _get_address_from_state(ostate)
            self.cache.add(address)
            self.issues.append(issue)
Пример #2
0
    def _analyze_state(state: GlobalState):
        """
        :param state: the current state
        :return: returns the issues for that corresponding state
        """
        instruction = state.get_current_instruction()

        annotations = cast(
            List[MultipleSendsAnnotation],
            list(state.get_annotations(MultipleSendsAnnotation)),
        )
        if len(annotations) == 0:
            state.annotate(MultipleSendsAnnotation())
            annotations = cast(
                List[MultipleSendsAnnotation],
                list(state.get_annotations(MultipleSendsAnnotation)),
            )

        call_offsets = annotations[0].call_offsets

        if instruction["opcode"] in [
                "CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"
        ]:
            call_offsets.append(state.get_current_instruction()["address"])

        else:  # RETURN or STOP

            for offset in call_offsets[1:]:
                try:
                    transaction_sequence = get_transaction_sequence(
                        state, state.mstate.constraints)
                except UnsatError:
                    continue
                description_tail = (
                    "This call is executed after a previous call in the same transaction. "
                    "Try to isolate each call, transfer or send into its own transaction."
                )

                issue = Issue(
                    contract=state.environment.active_account.contract_name,
                    function_name=state.environment.active_function_name,
                    address=offset,
                    swc_id=MULTIPLE_SENDS,
                    bytecode=state.environment.code.bytecode,
                    title="Multiple Calls in a Single Transaction",
                    severity="Low",
                    description_head=
                    "Multiple calls are executed in the same transaction.",
                    description_tail=description_tail,
                    gas_used=(state.mstate.min_gas_used,
                              state.mstate.max_gas_used),
                    transaction_sequence=transaction_sequence,
                )

                return [issue]

        return []
    def get_issue(self, global_state: GlobalState) -> Optional[Issue]:
        if not self.state_change_states:
            return None

        severity = "Medium" if self.user_defined_address else "Low"
        address = global_state.get_current_instruction()["address"]
        logging.debug(
            "[EXTERNAL_CALLS] Detected state changes at addresses: {}".format(
                address))
        description_head = (
            "The contract account state is changed after an external call. ")
        description_tail = (
            "Consider that the called contract could re-enter the function before this "
            "state change takes place. This can lead to business logic vulnerabilities."
        )

        return Issue(
            contract=global_state.environment.active_account.contract_name,
            function_name=global_state.environment.active_function_name,
            address=address,
            title="State change after external call",
            severity=severity,
            description_head=description_head,
            description_tail=description_tail,
            swc_id=REENTRANCY,
            bytecode=global_state.environment.code.bytecode,
        )
Пример #4
0
    def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord:
        assert len(state.mstate.stack) == len(record.stack)
        """ Runs taint analysis on a state """
        record.add_state(state)
        new_record = record.clone()

        # Apply Change
        op = state.get_current_instruction()["opcode"]

        if op in TaintRunner.stack_taint_table.keys():
            mutator = TaintRunner.stack_taint_table[op]
            TaintRunner.mutate_stack(new_record, mutator)
        elif op.startswith("PUSH"):
            TaintRunner.mutate_push(op, new_record)
        elif op.startswith("DUP"):
            TaintRunner.mutate_dup(op, new_record)
        elif op.startswith("SWAP"):
            TaintRunner.mutate_swap(op, new_record)
        elif op is "MLOAD":
            TaintRunner.mutate_mload(new_record, state.mstate.stack[-1])
        elif op.startswith("MSTORE"):
            TaintRunner.mutate_mstore(new_record, state.mstate.stack[-1])
        elif op is "SLOAD":
            TaintRunner.mutate_sload(new_record, state.mstate.stack[-1])
        elif op is "SSTORE":
            TaintRunner.mutate_sstore(new_record, state.mstate.stack[-1])
        elif op.startswith("LOG"):
            TaintRunner.mutate_log(new_record, op)
        elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"):
            TaintRunner.mutate_call(new_record, op)
        else:
            logging.debug("Unknown operation encountered: {}".format(op))

        return new_record
Пример #5
0
        def jumpi_hook(state: GlobalState):
            address = state.get_current_instruction()["address"]

            annotation = get_dependency_annotation(state)
            annotation.path.append(address)

            _check_basic_block(address, annotation)
Пример #6
0
    def _execute(self, state: GlobalState) -> None:
        if state.get_current_instruction()["address"] in self.cache:
            return
        issues = self._analyze_state(state)

        annotation = get_potential_issues_annotation(state)
        annotation.potential_issues.extend(issues)
Пример #7
0
 def _execute(self, state: GlobalState) -> None:
     if state.get_current_instruction()["address"] in self._cache:
         return
     issues = self._analyze_state(state)
     for issue in issues:
         self._cache.add(issue.address)
     self._issues.extend(issues)
Пример #8
0
        def world_state_filter_hook(state: GlobalState):

            if isinstance(state.current_transaction,
                          ContractCreationTransaction):
                # Reset iteration variable
                self.iteration = 0
                return

            world_state_annotation = get_ws_dependency_annotation(state)
            annotation = get_dependency_annotation(state)

            # Reset the state annotation except for storage written which is carried on to
            # the next transaction

            annotation.path = [0]
            annotation.storage_loaded = []
            annotation.has_call = False

            world_state_annotation.annotations_stack.append(annotation)

            log.debug(
                "Iteration {}: Adding world state at address {}, end of function {}.\nDependency map: {}\nStorage written: {}"
                .format(
                    self.iteration,
                    state.get_current_instruction()["address"],
                    state.node.function_name,
                    self.dependency_map,
                    annotation.storage_written[self.iteration],
                ))
Пример #9
0
def test_delegate_call(sym_mock, concrete_mock, curr_instruction):
    # arrange
    # sym_mock = mocker.patch.object(delegatecall, "_symbolic_call")
    # concrete_mock = mocker.patch.object(delegatecall, "_concrete_call")
    sym_mock.return_value = []
    concrete_mock.return_value = []
    curr_instruction.return_value = {"address": "0x10"}

    active_account = Account("0x10")
    active_account.code = Disassembly("00")

    environment = Environment(active_account, None, None, None, None, None)
    state = GlobalState(None, environment, Node)
    state.mstate.memory = ["placeholder", "calldata_bling_0"]
    state.mstate.stack = [1, 2, 3]
    assert state.get_current_instruction() == {"address": "0x10"}

    node = Node("example")
    node.contract_name = "the contract name"
    node.function_name = "fallback"

    to = Variable("storage_1", VarType.SYMBOLIC)
    call = Call(node, state, None, "DELEGATECALL", to, None)

    statespace = MagicMock()
    statespace.calls = [call]

    # act
    execute(statespace)

    # assert
    assert concrete_mock.call_count == 1
    assert sym_mock.call_count == 1
Пример #10
0
    def _execute(self, state: GlobalState) -> None:
        """Executes analysis module for integer underflow and integer overflow.

        :param state: Statespace to analyse
        :return: Found issues
        """

        address = _get_address_from_state(state)

        if address in self.cache:
            return

        opcode = state.get_current_instruction()["opcode"]

        funcs = {
            "ADD": [self._handle_add],
            "SUB": [self._handle_sub],
            "MUL": [self._handle_mul],
            "SSTORE": [self._handle_sstore],
            "JUMPI": [self._handle_jumpi],
            "CALL": [self._handle_call],
            "RETURN": [self._handle_return, self._handle_transaction_end],
            "STOP": [self._handle_transaction_end],
            "EXP": [self._handle_exp],
        }
        for func in funcs[opcode]:
            func(state)
Пример #11
0
    def _analyze_state(global_state: GlobalState) -> List[Issue]:

        annotations = cast(
            List[StateChangeCallsAnnotation],
            list(global_state.get_annotations(StateChangeCallsAnnotation)),
        )
        op_code = global_state.get_current_instruction()["opcode"]

        if len(annotations) == 0:
            if op_code in ("SSTORE", "CREATE", "CREATE2"):
                return []
        if op_code in ("SSTORE", "CREATE", "CREATE2"):
            for annotation in annotations:
                annotation.state_change_states.append(global_state)

        # Record state changes following from a transfer of ether
        if op_code in ("CALL", "DELEGATECALL", "CALLCODE"):
            value = global_state.mstate.stack[-3]  # type: BitVec
            if StateChange._balance_change(value, global_state):
                for annotation in annotations:
                    annotation.state_change_states.append(global_state)

        # Record external calls
        if op_code in ("CALL", "DELEGATECALL", "CALLCODE"):
            StateChange._add_external_call(global_state)

        # Check for vulnerabilities
        vulnerabilities = []
        for annotation in annotations:
            if not annotation.state_change_states:
                continue
            issue = annotation.get_issue(global_state)
            if issue:
                vulnerabilities.append(issue)
        return vulnerabilities
Пример #12
0
 def __init__(self, call_state: GlobalState, constraints: List) -> None:
     """
     Initialize DelegateCall Annotation
     :param call_state: Call state
     """
     self.call_state = call_state
     self.constraints = constraints
     self.return_value = call_state.new_bitvec(
         "retval_{}".format(call_state.get_current_instruction()["address"]), 256
     )
Пример #13
0
    def execute(self, state: GlobalState):
        """Executes analysis module for integer underflow and integer overflow.

        :param state: Statespace to analyse
        :return: Found issues
        """
        address = _get_address_from_state(state)
        has_overflow = self._overflow_cache.get(address, False)
        has_underflow = self._underflow_cache.get(address, False)
        if has_overflow or has_underflow:
            return
        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)
Пример #14
0
def get_call_from_state(state: GlobalState) -> Union[Call, None]:
    """

    :param state:
    :return:
    """
    instruction = state.get_current_instruction()

    op = instruction["opcode"]

    stack = state.mstate.stack

    if op in ("CALL", "CALLCODE"):
        gas, to, value, meminstart, meminsz, memoutstart, memoutsz = (
            get_variable(stack[-1]),
            get_variable(stack[-2]),
            get_variable(stack[-3]),
            get_variable(stack[-4]),
            get_variable(stack[-5]),
            get_variable(stack[-6]),
            get_variable(stack[-7]),
        )

        if to.type == VarType.CONCRETE and to.val < 5:
            return None

        if meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE:
            return Call(
                state.node,
                state,
                None,
                op,
                to,
                gas,
                value,
                state.mstate.memory[meminstart.val:meminsz.val * 4],
            )
        else:
            return Call(state.node, state, None, op, to, gas, value)

    else:
        gas, to, meminstart, meminsz, memoutstart, memoutsz = (
            get_variable(stack[-1]),
            get_variable(stack[-2]),
            get_variable(stack[-3]),
            get_variable(stack[-4]),
            get_variable(stack[-5]),
            get_variable(stack[-6]),
        )

        return Call(state.node, state, None, op, to, gas)
Пример #15
0
    def get_issue(
        self, global_state: GlobalState, detector: DetectionModule
    ) -> Optional[PotentialIssue]:
        if not self.state_change_states:
            return None
        constraints = Constraints()
        gas = self.call_state.mstate.stack[-1]
        to = self.call_state.mstate.stack[-2]
        constraints += [
            UGT(gas, symbol_factory.BitVecVal(2300, 256)),
            Or(
                to > symbol_factory.BitVecVal(16, 256),
                to == symbol_factory.BitVecVal(0, 256),
            ),
        ]
        if self.user_defined_address:
            constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF]

        try:
            solver.get_transaction_sequence(
                global_state, constraints + global_state.mstate.constraints
            )
        except UnsatError:
            return None

        severity = "Medium" if self.user_defined_address else "Low"
        address = global_state.get_current_instruction()["address"]
        logging.debug(
            "[EXTERNAL_CALLS] Detected state changes at addresses: {}".format(address)
        )
        description_head = (
            "The contract account state is changed after an external call. "
        )
        description_tail = (
            "Consider that the called contract could re-enter the function before this "
            "state change takes place. This can lead to business logic vulnerabilities."
        )

        return PotentialIssue(
            contract=global_state.environment.active_account.contract_name,
            function_name=global_state.environment.active_function_name,
            address=address,
            title="State change after external call",
            severity=severity,
            description_head=description_head,
            description_tail=description_tail,
            swc_id=REENTRANCY,
            bytecode=global_state.environment.code.bytecode,
            constraints=constraints,
            detector=detector,
        )
Пример #16
0
def native_call(
    global_state: GlobalState,
    callee_address: Union[str, BitVec],
    call_data: BaseCalldata,
    memory_out_offset: Union[int, Expression],
    memory_out_size: Union[int, Expression],
) -> Optional[List[GlobalState]]:

    if (
        isinstance(callee_address, BitVec)
        or not 0 < int(callee_address, 16) <= PRECOMPILE_COUNT
    ):
        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 = calculate_native_gas(
        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]

    retval = global_state.new_bitvec(
        "retval_" + str(global_state.get_current_instruction()["address"]), 256
    )
    global_state.mstate.stack.append(retval)
    global_state.node.constraints.append(retval == 1)
    return [global_state]
Пример #17
0
def _analyze_states(state: GlobalState) -> List[Issue]:
    """
    :param state: the current state
    :return: returns the issues for that corresponding state
    """
    issues = []
    op_code = state.get_current_instruction()["opcode"]
    annotations = cast(
        List[DelegateCallAnnotation],
        list(state.get_annotations(DelegateCallAnnotation)),
    )

    if len(annotations) == 0 and op_code in ("RETURN", "STOP"):
        return []

    if op_code == "DELEGATECALL":
        gas = state.mstate.stack[-1]
        to = state.mstate.stack[-2]

        constraints = [
            to == ATTACKER_ADDRESS,
            UGT(gas, symbol_factory.BitVecVal(2300, 256)),
        ]

        for tx in state.world_state.transaction_sequence:
            if not isinstance(tx, ContractCreationTransaction):
                constraints.append(tx.caller == ATTACKER_ADDRESS)

        state.annotate(DelegateCallAnnotation(state, constraints))

        return []
    else:
        for annotation in annotations:
            try:
                transaction_sequence = solver.get_transaction_sequence(
                    state,
                    state.mstate.constraints
                    + annotation.constraints
                    + [annotation.return_value == 1],
                )
                issues.append(
                    annotation.get_issue(
                        state, transaction_sequence=transaction_sequence
                    )
                )
            except UnsatError:
                continue

        return issues
Пример #18
0
    def _analyze_state(self, state: GlobalState):
        """

        :param state:
        :return:
        """
        topic, size, mem_start = state.mstate.stack[-3:]

        if topic.symbolic or topic.value != assertion_failed_hash:
            return []

        message = None
        if not mem_start.symbolic and not size.symbolic:
            message = eth_abi.decode_single(
                "string",
                bytes(state.mstate.memory[mem_start.value +
                                          32:mem_start.value + size.value]),
            ).decode("utf8")

        description_head = "A user-provided assertion failed."
        if message:
            description_tail = "A user-provided assertion failed with message '{}'. Make sure the user-provided assertion is correct.".format(
                message)
        else:
            description_tail = "A user-provided assertion failed. Make sure the user-provided assertion is correct."

        address = state.get_current_instruction()["address"]
        issue = PotentialIssue(
            contract=state.environment.active_account.contract_name,
            function_name=state.environment.active_function_name,
            address=address,
            swc_id=ASSERT_VIOLATION,
            title="Assertion Failed",
            bytecode=state.environment.code.bytecode,
            severity="Medium",
            description_head=description_head,
            description_tail=description_tail,
            constraints=[],
            detector=self,
        )

        return [issue]
Пример #19
0
def _analyze_states(state: GlobalState) -> List[Issue]:
    """
    :param state: the current state
    :return: returns the issues for that corresponding state
    """
    call = get_call_from_state(state)
    if call is None:
        return []
    issues = []  # type: List[Issue]

    if call.type is not "DELEGATECALL":
        return []
    if call.node.function_name is not "fallback":
        return []

    state = call.state
    address = state.get_current_instruction()["address"]
    meminstart = get_variable(state.mstate.stack[-3])

    if meminstart.type == VarType.CONCRETE:
        issues += _concrete_call(call, state, address, meminstart)

    return issues
Пример #20
0
    def _analyze_states(self, state: GlobalState) -> List[Issue]:
        """
        :param state: the current state
        :return: returns the issues for that corresponding state
        """

        opcode = state.get_current_instruction()["opcode"]
        address = state.get_current_instruction()["address"]

        if opcode == "JUMPI":

            target = util.get_concrete_int(state.mstate.stack[-1])

            transaction = state.current_transaction
            if state.current_transaction in self._jumpdest_count:

                try:
                    self._jumpdest_count[transaction][target] += 1
                    if self._jumpdest_count[transaction][target] == 3:

                        annotation = (
                            LoopAnnotation(address, target)
                            if target > address
                            else LoopAnnotation(target, address)
                        )

                        state.annotate(annotation)
                except KeyError:
                    self._jumpdest_count[transaction][target] = 0

            else:
                self._jumpdest_count[transaction] = {}
                self._jumpdest_count[transaction][target] = 0

        else:

            annotations = cast(
                List[LoopAnnotation], list(state.get_annotations(LoopAnnotation))
            )

            for annotation in annotations:

                if annotation.contains(address):

                    operation = (
                        "A storage modification"
                        if opcode == "SSTORE"
                        else "An external call"
                    )

                    description_head = (
                        "Potential denial-of-service if block gas limit is reached."
                    )
                    description_tail = "{} is executed in a loop.".format(operation)

                    issue = Issue(
                        contract=state.environment.active_account.contract_name,
                        function_name=state.environment.active_function_name,
                        address=annotation.loop_start,
                        swc_id=DOS_WITH_BLOCK_GAS_LIMIT,
                        bytecode=state.environment.code.bytecode,
                        title="Potential denial-of-service if block gas limit is reached",
                        severity="Low",
                        description_head=description_head,
                        description_tail=description_tail,
                        gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
                    )
                    return [issue]

        return []
Пример #21
0
    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))])

            solver.get_transaction_sequence(
                state, constraints + state.mstate.constraints)

            # Check whether we can also set the callee address

            try:
                constraints += [to == ATTACKER_ADDRESS]

                for tx in state.world_state.transaction_sequence:
                    if not isinstance(tx, ContractCreationTransaction):
                        constraints.append(tx.caller == ATTACKER_ADDRESS)

                solver.get_transaction_sequence(
                    state, constraints + state.mstate.constraints)

                description_head = "A call to a user-supplied address is executed."
                description_tail = (
                    "The callee address of an external message call can be set by "
                    "the caller. Note that the callee can contain arbitrary code and may re-enter any function "
                    "in this contract. Review the business logic carefully to prevent averse effects on the "
                    "contract state.")

                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="Medium",
                    description_head=description_head,
                    description_tail=description_tail,
                    constraints=constraints,
                    detector=self,
                )

            except UnsatError:
                if _is_precompile_call(state):
                    return []

                log.debug(
                    "[EXTERNAL_CALLS] Callee address cannot be modified. Reporting informational issue."
                )

                description_head = "The contract executes an external message call."
                description_tail = (
                    "An external function call to a fixed contract address is executed. Make sure "
                    "that the callee contract has been reviewed carefully.")

                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 Fixed 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]
Пример #22
0
def _analyze_states(state: GlobalState) -> list:
    """

    :param state:
    :return:
    """

    issues = []

    if is_prehook():

        opcode = state.get_current_instruction()["opcode"]

        if opcode in final_ops:

            for annotation in state.annotations:

                if isinstance(annotation, PredictablePathAnnotation):
                    description = (
                        "The " + annotation.operation +
                        " is used in to determine a control flow decision. ")
                    description += (
                        "Note that the values of variables like coinbase, gaslimit, block number and timestamp "
                        "are predictable and can be manipulated by a malicious miner. Also keep in mind that attackers "
                        "know hashes of earlier blocks. Don't use any of those environment variables for random number "
                        "generation or to make critical control flow decisions."
                    )
                    """
                    Usually report low severity except in cases where the hash of a previous block is used to
                    determine control flow. 
                    """

                    severity = "Medium" if "hash" in annotation.operation else "Low"
                    """
                    Note: We report the location of the JUMPI that lead to this path. Usually this maps to an if or
                    require statement.
                    """

                    swc_id = (TIMESTAMP_DEPENDENCE if "timestamp"
                              in annotation.operation else WEAK_RANDOMNESS)

                    issue = Issue(
                        contract=state.environment.active_account.
                        contract_name,
                        function_name=state.environment.active_function_name,
                        address=annotation.location,
                        swc_id=swc_id,
                        bytecode=state.environment.code.bytecode,
                        title="Dependence on predictable environment variable",
                        severity=severity,
                        description_head=
                        "A control flow decision is made based on a predictable variable.",
                        description_tail=description,
                        gas_used=(state.mstate.min_gas_used,
                                  state.mstate.max_gas_used),
                    )
                    issues.append(issue)

        elif opcode == "JUMPI":

            # Look for predictable state variables in jump condition

            for annotation in state.mstate.stack[-2].annotations:

                if isinstance(annotation, PredictableValueAnnotation):
                    state.annotate(
                        PredictablePathAnnotation(
                            annotation.operation,
                            state.get_current_instruction()["address"],
                        ))
                    break

        elif opcode == "BLOCKHASH":

            param = state.mstate.stack[-1]

            try:
                constraint = [
                    ULT(param, state.environment.block_number),
                    ULT(
                        state.environment.block_number,
                        symbol_factory.BitVecVal(2**255, 256),
                    ),
                ]

                # Why the second constraint? Because without it Z3 returns a solution where param overflows.

                solver.get_model(constraint)
                state.annotate(OldBlockNumberUsedAnnotation())

            except UnsatError:
                pass

    else:
        # we're in post hook

        opcode = state.environment.code.instruction_list[state.mstate.pc -
                                                         1]["opcode"]

        if opcode == "BLOCKHASH":
            # if we're in the post hook of a BLOCKHASH op, check if an old block number was used to create it.

            annotations = cast(
                List[OldBlockNumberUsedAnnotation],
                list(state.get_annotations(OldBlockNumberUsedAnnotation)),
            )

            if len(annotations):
                state.mstate.stack[-1].annotate(
                    PredictableValueAnnotation(
                        "block hash of a previous block"))
        else:
            # Always create an annotation when COINBASE, GASLIMIT, TIMESTAMP or NUMBER is executed.

            state.mstate.stack[-1].annotate(
                PredictableValueAnnotation(
                    "block.{} environment variable".format(opcode.lower())))

    return issues
Пример #23
0
    def _analyze_state(self, state: GlobalState) -> List[Issue]:
        """
        :param state: the current state
        :return: returns the issues for that corresponding state
        """

        opcode = state.get_current_instruction()["opcode"]
        address = state.get_current_instruction()["address"]

        annotations = cast(List[VisitsAnnotation],
                           list(state.get_annotations(VisitsAnnotation)))

        if len(annotations) == 0:
            annotation = VisitsAnnotation()
            state.annotate(annotation)
        else:
            annotation = annotations[0]

        if opcode in ["JUMP", "JUMPI"]:

            if annotation.loop_start is not None:
                return []
            try:
                target = util.get_concrete_int(state.mstate.stack[-1])
            except TypeError:
                log.debug("Symbolic target encountered in dos module")
                return []
            if target in annotation.jump_targets:
                annotation.jump_targets[target] += 1
            else:
                annotation.jump_targets[target] = 1

            if annotation.jump_targets[target] > min(
                    2, analysis_args.loop_bound - 1):
                annotation.loop_start = address

        elif annotation.loop_start is not None:

            if opcode == "CALL":
                operation = "A message call"
            else:
                operation = "A storage modification"

            description_head = (
                "Potential denial-of-service if block gas limit is reached.")
            description_tail = "{} is executed in a loop. Be aware that the transaction may fail to execute if the loop is unbounded and the necessary gas exceeds the block gas limit.".format(
                operation)

            try:
                transaction_sequence = get_transaction_sequence(
                    state, state.mstate.constraints)
            except UnsatError:
                return []

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=annotation.loop_start,
                swc_id=DOS_WITH_BLOCK_GAS_LIMIT,
                bytecode=state.environment.code.bytecode,
                title=
                "Potential denial-of-service if block gas limit is reached",
                severity="Low",
                description_head=description_head,
                description_tail=description_tail,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
                transaction_sequence=transaction_sequence,
            )

            return [issue]

        return []
Пример #24
0
def _analyze_state(state: GlobalState):
    """
    :param state: the current state
    :return: returns the issues for that corresponding state
    """
    instruction = state.get_current_instruction()

    annotations = cast(
        List[MultipleSendsAnnotation],
        list(state.get_annotations(MultipleSendsAnnotation)),
    )
    if len(annotations) == 0:
        log.debug("Creating annotation for state")
        state.annotate(MultipleSendsAnnotation())
        annotations = cast(
            List[MultipleSendsAnnotation],
            list(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:

            description_tail = (
                "Consecutive calls are executed at the following bytecode offsets:\n"
            )

            for call in calls:
                description_tail += "Offset: {}\n".format(
                    call.state.get_current_instruction()["address"])

            description_tail += (
                "Try to isolate each external call into its own transaction,"
                " as external calls can fail accidentally or deliberately.\n")

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=instruction["address"],
                swc_id=MULTIPLE_SENDS,
                bytecode=state.environment.code.bytecode,
                title="Multiple Calls in a Single Transaction",
                severity="Medium",
                description_head=
                "Multiple sends are executed in one transaction.",
                description_tail=description_tail,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )

            return [issue]

    return []
Пример #25
0
def _analyze_state(state: GlobalState) -> list:
    instruction = state.get_current_instruction()

    annotations = cast(
        List[UncheckedRetvalAnnotation],
        [a for a in state.get_annotations(UncheckedRetvalAnnotation)],
    )
    if len(annotations) == 0:
        state.annotate(UncheckedRetvalAnnotation())
        annotations = cast(
            List[UncheckedRetvalAnnotation],
            [a for a in state.get_annotations(UncheckedRetvalAnnotation)],
        )

    retvals = annotations[0].retvals

    if instruction["opcode"] in ("STOP", "RETURN"):
        issues = []
        for retval in retvals:
            try:
                solver.get_model(state.mstate.constraints +
                                 [retval["retval"] == 0])
            except UnsatError:
                continue

            description_tail = (
                "External calls return a boolean value. If the callee contract halts with an exception, 'false' is "
                "returned and execution continues in the caller. It is usually recommended to wrap external calls "
                "into a require statement to prevent unexpected states.")

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=retval["address"],
                bytecode=state.environment.code.bytecode,
                title="Unchecked Call Return Value",
                swc_id=UNCHECKED_RET_VAL,
                severity="Low",
                description_head=
                "The return value of a message call is not checked.",
                description_tail=description_tail,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )

            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]
        # Use Typed Dict after release of mypy 0.670 and remove type ignore
        retvals.append(
            {  # type: ignore
                "address": state.instruction["address"] - 1,
                "retval": retval,
            }
        )

    return []