Example #1
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 []
Example #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
Example #3
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
Example #4
0
    def _handle_transaction_end(self, state: GlobalState) -> None:
        for annotation in cast(
                List[OverUnderflowStateAnnotation],
                state.get_annotations(OverUnderflowStateAnnotation),
        ):

            ostate = annotation.overflowing_state
            address = _get_address_from_state(ostate)

            if annotation.operator == "subtraction" and self._underflow_cache.get(
                    address, False):
                continue

            if annotation.operator != "subtraction" and self._overflow_cache.get(
                    address, False):
                continue

            try:
                # This check can be disabled if the contraints are to difficult for z3 to solve
                # within any reasonable time.
                if DISABLE_EFFECT_CHECK:
                    constraints = ostate.mstate.constraints + [
                        annotation.constraint
                    ]
                else:
                    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),
            )

            issue.debug = json.dumps(transaction_sequence, indent=4)

            if annotation.operator == "subtraction":
                self._underflow_cache[address] = True
            else:
                self._overflow_cache[address] = True
            self._issues.append(issue)
 def world_state_filter_hook(global_state: GlobalState):
     if And(*global_state.mstate.constraints[:] + [
             global_state.environment.callvalue >
             symbol_factory.BitVecVal(0, 256)
     ]).is_false:
         return
     if isinstance(global_state.current_transaction,
                   ContractCreationTransaction):
         return
     if len(list(
             global_state.get_annotations(MutationAnnotation))) == 0:
         raise PluginSkipWorldState
Example #6
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]
Example #7
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
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 []
Example #9
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
Example #10
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 []
Example #11
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 []
Example #12
0
    def execute_state(
            self, global_state: GlobalState
    ) -> Tuple[List[GlobalState], Optional[str]]:
        """Execute a single instruction in global_state.

        :param global_state:
        :return: A list of successor states.
        """
        # Execute hooks
        for hook in self._execute_state_hooks:
            hook(global_state)

        instructions = global_state.environment.code.instruction_list

        try:
            op_code = instructions[global_state.mstate.pc]["opcode"]
        except IndexError:
            self._add_world_state(global_state)
            return [], None
        if len(global_state.mstate.stack) < get_required_stack_elements(
                op_code):
            error_msg = ("Stack Underflow Exception due to insufficient "
                         "stack elements for the address {}".format(
                             instructions[global_state.mstate.pc]["address"]))
            new_global_states = self.handle_vm_exception(
                global_state, op_code, error_msg)
            self._execute_post_hook(op_code, new_global_states)
            return new_global_states, op_code

        try:
            self._execute_pre_hook(op_code, global_state)
        except PluginSkipState:
            self._add_world_state(global_state)
            return [], None

        try:
            new_global_states = Instruction(op_code, self.dynamic_loader,
                                            self.iprof).evaluate(global_state)

        except VmException as e:
            new_global_states = self.handle_vm_exception(
                global_state, op_code, str(e))

        except TransactionStartSignal as start_signal:
            # Setup new global state
            new_global_state = start_signal.transaction.initial_global_state()

            new_global_state.transaction_stack = copy(
                global_state.transaction_stack) + [
                    (start_signal.transaction, global_state)
                ]
            new_global_state.node = global_state.node
            new_global_state.mstate.constraints = (
                start_signal.global_state.mstate.constraints)

            log.debug("Starting new transaction %s", start_signal.transaction)

            return [new_global_state], op_code

        except TransactionEndSignal as end_signal:
            transaction, return_global_state = end_signal.global_state.transaction_stack[
                -1]

            log.debug("Ending transaction %s.", transaction)

            if return_global_state is None:
                if (not isinstance(transaction, ContractCreationTransaction)
                        or transaction.return_data) and not end_signal.revert:
                    check_potential_issues(global_state)

                    end_signal.global_state.world_state.node = global_state.node
                    self._add_world_state(end_signal.global_state)
                new_global_states = []
            else:
                # First execute the post hook for the transaction ending instruction
                self._execute_post_hook(op_code, [end_signal.global_state])

                # Propogate codecall based annotations
                if return_global_state.get_current_instruction()["opcode"] in (
                        "DELEGATECALL",
                        "CALLCODE",
                ):
                    new_annotations = [
                        annotation for annotation in
                        global_state.get_annotations(MutationAnnotation)
                    ]
                    return_global_state.add_annotations(new_annotations)

                new_global_states = self._end_message_call(
                    copy(return_global_state),
                    global_state,
                    revert_changes=False or end_signal.revert,
                    return_data=transaction.return_data,
                )

        self._execute_post_hook(op_code, new_global_states)

        return new_global_states, op_code
Example #13
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 []