Exemplo n.º 1
0
    def _add_external_call(global_state: GlobalState) -> None:
        gas = global_state.mstate.stack[-1]
        to = global_state.mstate.stack[-2]
        try:
            constraints = copy(global_state.mstate.constraints)
            solver.get_model(constraints + [
                UGT(gas, symbol_factory.BitVecVal(2300, 256)),
                Or(
                    to > symbol_factory.BitVecVal(16, 256),
                    to == symbol_factory.BitVecVal(0, 256),
                ),
            ])

            # Check whether we can also set the callee address
            try:
                constraints += [
                    to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
                ]
                solver.get_model(constraints)

                global_state.annotate(
                    StateChangeCallsAnnotation(global_state, True))
            except UnsatError:
                global_state.annotate(
                    StateChangeCallsAnnotation(global_state, False))
        except UnsatError:
            pass
Exemplo n.º 2
0
def get_dependency_annotation(state: GlobalState) -> DependencyAnnotation:
    """ Returns a dependency annotation

    :param state: A global state object
    """

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

    if len(annotations) == 0:
        """FIXME: Hack for carrying over state annotations from the STOP and RETURN states of
        the previous states. The states are pushed on a stack in the world state annotation
        and popped off the stack in the subsequent iteration. This might break if any
        other strategy than bfs is used (?).
        """

        try:
            world_state_annotation = get_ws_dependency_annotation(state)
            annotation = world_state_annotation.annotations_stack.pop()
        except IndexError:
            annotation = DependencyAnnotation()

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

    return annotation
Exemplo n.º 3
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 []
Exemplo n.º 4
0
def _get_overflowunderflow_state_annotation(
        state: GlobalState) -> OverUnderflowStateAnnotation:
    state_annotations = cast(
        List[OverUnderflowStateAnnotation],
        list(state.get_annotations(OverUnderflowStateAnnotation)),
    )

    if len(state_annotations) == 0:
        state_annotation = OverUnderflowStateAnnotation()
        state.annotate(state_annotation)
        return state_annotation
    else:
        return state_annotations[0]
Exemplo n.º 5
0
 def _handle_sstore(state: GlobalState) -> None:
     stack = state.mstate.stack
     value = stack[-2]
     if not isinstance(value, Expression):
         return
     for annotation in value.annotations:
         if not isinstance(annotation, OverUnderflowAnnotation):
             continue
         state.annotate(
             OverUnderflowStateAnnotation(
                 annotation.overflowing_state,
                 annotation.operator,
                 annotation.constraint,
             ))
Exemplo n.º 6
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
Exemplo n.º 7
0
def get_potential_issues_annotation(
        state: GlobalState) -> PotentialIssuesAnnotation:
    """
    Returns the potential issues annotation of the given global state, and creates one if
    one does not already exist.

    :param state: The global state
    :return:
    """
    for annotation in state.annotations:
        if isinstance(annotation, PotentialIssuesAnnotation):
            return annotation

    annotation = PotentialIssuesAnnotation()
    state.annotate(annotation)
    return annotation
Exemplo n.º 8
0
 def _handle_return(state: GlobalState) -> None:
     """
     Adds all the annotations into the state which correspond to the
     locations in the memory returned by RETURN opcode.
     :param state: The Global State
     """
     stack = state.mstate.stack
     try:
         offset, length = get_concrete_int(stack[-1]), get_concrete_int(
             stack[-2])
     except TypeError:
         return
     for element in state.mstate.memory[offset:offset + length]:
         if not isinstance(element, Expression):
             continue
         for annotation in element.annotations:
             if isinstance(annotation, OverUnderflowAnnotation):
                 state.annotate(
                     OverUnderflowStateAnnotation(
                         annotation.overflowing_state,
                         annotation.operator,
                         annotation.constraint,
                     ))
Exemplo n.º 9
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 []
Exemplo n.º 10
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
Exemplo n.º 11
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 []
Exemplo n.º 12
0
 def mutator_hook(global_state: GlobalState):
     global_state.annotate(MutationAnnotation())
Exemplo n.º 13
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 []
Exemplo n.º 14
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 []