Exemple #1
0
def execute(statespace):

    logging.debug("Executing module: EXCEPTIONS")

    issues = []

    for k in statespace.nodes:
        node = statespace.nodes[k]

        for state in node.states:

            instruction = state.get_current_instruction()

            if (instruction['opcode'] == "ASSERT_FAIL"):

                try:
                    model = solver.get_model(node.constraints)
                    address = state.get_current_instruction()['address']

                    description = "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. "
                    description += "This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. "

                    debug = "The exception is triggered under the following conditions:\n\n"

                    debug += solver.pretty_print_model(model)

                    issues.append(
                        Issue(node.contract_name, node.function_name, address,
                              "Exception state", "Informational", description,
                              debug))

                except UnsatError:
                    logging.debug("[EXCEPTIONS] no model found")

    return issues
Exemple #2
0
def _check_integer_overflow(statespace, state, node):
    """
    Checks for integer overflow
    :param statespace: statespace that is being examined
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []

    # Check the instruction
    instruction = state.get_current_instruction()
    if instruction['opcode'] not in ("ADD", "MUL"):
        return issues

    # Formulate overflow constraints
    stack = state.mstate.stack
    op0, op1 = stack[-1], stack[-2]

    # An integer overflow is possible if op0 + op1 or op0 * op1 > MAX_UINT
    # Do a type check
    allowed_types = [int, BitVecRef, BitVecNumRef]
    if not (type(op0) in allowed_types and type(op1) in allowed_types):
        return issues

    # Change ints to BitVec
    if type(op0) is int:
        op0 = BitVecVal(op0, 256)
    if type(op1) is int:
        op1 = BitVecVal(op1, 256)

    # Formulate expression
    if instruction['opcode'] == "ADD":
        expr = op0 + op1
    else:
        expr = op1 * op0

    # Check satisfiable
    constraint = Or(ULT(expr, op0), ULT(expr, op1))
    model = _try_constraints(node.constraints, [constraint])

    if model is None:
        logging.debug("[INTEGER_OVERFLOW] no model found")
        return issues

    if not _verify_integer_overflow(statespace, node, expr, state, model,
                                    constraint, op0, op1):
        return issues

    # Build issue
    issue = Issue(node.contract_name, node.function_name,
                  instruction['address'], "Integer Overflow ", "Warning")

    issue.description = "A possible integer overflow exists in the function `{}`.\n" \
                        "The addition or multiplication may result in a value higher than the maximum representable integer.".format(
        node.function_name)
    issue.debug = solver.pretty_print_model(model)
    issues.append(issue)

    return issues
Exemple #3
0
def _analyze_state(state, node):
    issues = []
    instruction = state.get_current_instruction()

    if instruction["opcode"] != "SUICIDE":
        return []

    to = state.mstate.stack[-1]

    logging.debug("[UNCHECKED_SUICIDE] suicide in function " +
                  node.function_name)
    description = ("The function `" + node.function_name +
                   "` executes the SUICIDE instruction. ")

    if "caller" in str(to):
        description += "The remaining Ether is sent to the caller's address.\n"
    elif "storage" in str(to):
        description += "The remaining Ether is sent to a stored address.\n"
    elif "calldata" in str(to):
        description += "The remaining Ether is sent to an address provided as a function argument.\n"
    elif type(to) == BitVecNumRef:
        description += "The remaining Ether is sent to: " + hex(
            to.as_long()) + "\n"
    else:
        description += "The remaining Ether is sent to: " + str(to) + "\n"

    not_creator_constraints = []
    if len(state.world_state.transaction_sequence) > 1:
        creator = state.world_state.transaction_sequence[0].caller
        for transaction in state.world_state.transaction_sequence[1:]:
            not_creator_constraints.append(
                Not(
                    Extract(159, 0, transaction.caller) == Extract(
                        159, 0, creator)))
            not_creator_constraints.append(
                Not(Extract(159, 0, transaction.caller) == 0))

    try:
        model = solver.get_model(node.constraints + not_creator_constraints)

        debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model)

        issue = Issue(
            contract=node.contract_name,
            function=node.function_name,
            address=instruction["address"],
            swc_id=UNPROTECTED_SELFDESTRUCT,
            title="Unchecked SUICIDE",
            _type="Warning",
            description=description,
            debug=debug,
        )
        issues.append(issue)
    except UnsatError:
        logging.debug("[UNCHECKED_SUICIDE] no model found")

    return issues
Exemple #4
0
def gen_debug(item):
    return ""
    (instruction, to, val, input_dep_val_x, input_dep_to_x, model) = item
    if model == None:
        return ""
    else:
        debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(
            model) + "\n\n\n"
        return debug
Exemple #5
0
def _check_integer_underflow(state, node):
    """
    Checks for integer underflow
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []
    instruction = state.get_current_instruction()

    if instruction['opcode'] == "SUB":

        stack = state.mstate.stack

        op0 = stack[-1]
        op1 = stack[-2]

        constraints = copy.deepcopy(node.constraints)

        # Filter for patterns that contain bening nteger underflows.

        # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
        # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0
        if type(op0) == int and type(op1) == int:
            return []
        if re.search(r'calldatasize_', str(op0)):
            return []
        if re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL):
            return []
        if re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL):
            return []

        logging.debug("[INTEGER_UNDERFLOW] Checking SUB {0}, {1} at address {2}".format(str(op0), str(op1),
                                                                                        str(instruction['address'])))

        allowed_types = [int, BitVecRef, BitVecNumRef]

        if type(op0) in allowed_types and type(op1) in allowed_types:
            constraints.append(UGT(op1, op0))

            try:

                model = solver.get_model(constraints)

                issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Underflow",
                              "Warning")

                issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                                                                                                                  "The subtraction may result in a value < 0."

                issue.debug = solver.pretty_print_model(model)
                issues.append(issue)

            except UnsatError:
                logging.debug("[INTEGER_UNDERFLOW] no model found")
    return issues
def solve(call):
    try:
        model = solver.get_model(call.node.constraints)
        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))
        pretty_model = solver.pretty_print_model(model)

        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: \n%s" %
                      pretty_model)
        return True

    except UnsatError:
        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] no model found")
        return False
Exemple #7
0
def _check_integer_overflow(statespace, state, node):
    """
    Checks for integer overflow
    :param statespace: statespace that is being examined
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []

    # Check the instruction
    instruction = state.get_current_instruction()
    if instruction['opcode'] != "ADD":
        return []

    constraints = copy.deepcopy(node.constraints)

    # Formulate overflow constraints
    stack = state.mstate.stack
    op0, op1 = stack[-1], stack[-2]

    # An integer overflow is possible if op0 + op1,
    constraints.append(UGT(op0 + op1, (2 ** 32) - 1))

    try:
        model = solver.get_model(constraints)

        # If we get to this point then there has been an integer overflow
        # Find out if the overflowed value is actually used
        interesting_usages = _search_children(statespace, node, (op0 + op1), index=node.states.index(state))

        # Stop if it isn't
        if len(interesting_usages) == 0:
            return issues

        issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow ",
                      "Warning")

        issue.description = "A possible integer overflow exists in the function {}.\n" \
                            "The addition may result in a value higher than the maximum representable integer.".format(node.function_name)
        issue.debug = solver.pretty_print_model(model)
        issues.append(issue)

    except UnsatError:
        logging.debug("[INTEGER_OVERFLOW] no model found")

    return issues
def solve(call: Call) -> bool:
    """

    :param call:
    :return:
    """
    try:
        model = solver.get_model(call.state.mstate.constraints)
        log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))
        pretty_model = solver.pretty_print_model(model)

        log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: \n%s" % pretty_model)
        return True

    except UnsatError:
        log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] no model found")
        return False
Exemple #9
0
def execute(statespace):

    logging.debug("Executing module: INTEGER")

    issues = []

    for k in statespace.nodes:
        node = statespace.nodes[k]

        for state in node.states:

            instruction = state.get_current_instruction()

            if (instruction['opcode'] == "SUB"):

                stack = state.mstate.stack

                op0 = stack[-1]
                op1 = stack[-2]

                constraints = copy.deepcopy(node.constraints)

                if type(op0) == int and type(op1) == int:
                    continue

                if (re.search(r'calldatasize_', str(op0))) \
                    or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)) \
                    or (re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL)):

                    # Filter for patterns that contain bening nteger underflows.

                    # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
                    # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0

                    continue

                logging.debug("[INTEGER_UNDERFLOW] Checking SUB " + str(op0) +
                              ", " + str(op1) + " at address " +
                              str(instruction['address']))

                allowed_types = [int, BitVecRef, BitVecNumRef]

                if type(op0) in allowed_types and type(op1) in allowed_types:
                    constraints.append(UGT(op1, op0))

                    try:

                        model = solver.get_model(constraints)

                        issue = Issue(node.contract_name, node.function_name,
                                      instruction['address'],
                                      "Integer Underflow", "Warning")

                        issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                            "The substraction may result in a value < 0."

                        issue.debug = solver.pretty_print_model(model)
                        issues.append(issue)

                    except UnsatError:
                        logging.debug("[INTEGER_UNDERFLOW] no model found")

    return issues
Exemple #10
0
def _check_integer_underflow(statespace, state, node):
    """
    Checks for integer underflow
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []
    instruction = state.get_current_instruction()
    if instruction['opcode'] == "SUB":

        stack = state.mstate.stack

        op0 = stack[-1]
        op1 = stack[-2]

        constraints = copy.deepcopy(node.constraints)

        # Filter for patterns that indicate benign underflows

        # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
        # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0
        if type(op0) == int and type(op1) == int:
            return []
        if re.search(r'calldatasize_', str(op0)):
            return []
        if re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(
                r'256\*.*If\(1', str(op1), re.DOTALL):
            return []
        if re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(
                r'32 \+.*calldata', str(op1), re.DOTALL):
            return []

        logging.debug(
            "[INTEGER_UNDERFLOW] Checking SUB {0}, {1} at address {2}".format(
                str(op0), str(op1), str(instruction['address'])))
        allowed_types = [int, BitVecRef, BitVecNumRef]

        if type(op0) in allowed_types and type(op1) in allowed_types:
            constraints.append(UGT(op1, op0))

            try:

                model = solver.get_model(constraints)

                # If we get to this point then there has been an integer overflow
                # Find out if the overflowed value is actually used
                interesting_usages = _search_children(
                    statespace,
                    node, (op0 - op1),
                    index=node.states.index(state))

                # Stop if it isn't
                if len(interesting_usages) == 0:
                    return issues

                issue = Issue(contract=node.contract_name,
                              function=node.function_name,
                              address=instruction['address'],
                              swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                              title="Integer Underflow",
                              _type="Warning")

                issue.description = "A possible integer underflow exists in the function `" + node.function_name + "`.\n" \
                                                                                                                   "The subtraction may result in a value < 0."

                issue.debug = solver.pretty_print_model(model)
                issues.append(issue)

            except UnsatError:
                logging.debug("[INTEGER_UNDERFLOW] no model found")
    return issues
Exemple #11
0
def execute(statespace):

    logging.debug("Executing module: ETHER_SEND")

    issues = []

    for call in statespace.calls:

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

        if "callvalue" in str(call.value):
            logging.debug("[ETHER_SEND] Skipping refund function")
            continue

        # We're only interested in calls that send Ether

        if call.value.type == VarType.CONCRETE and call.value.val == 0:
            continue

        interesting = False

        description = "In the function `" + call.node.function_name + "` "

        if re.search(r"caller", str(call.to)):
            description += "a non-zero amount of Ether is sent to msg.sender.\n"
            interesting = True

        elif re.search(r"calldata", str(call.to)):
            description += "a non-zero amount of Ether is sent to an address taken from function arguments.\n"
            interesting = True

        else:
            m = re.search(r"storage_([a-z0-9_&^]+)", str(call.to))

            if m:
                idx = m.group(1)

                description += (
                    "a non-zero amount of Ether is sent to an address taken from storage slot "
                    + str(idx) + ".\n")

                func = statespace.find_storage_write(
                    state.environment.active_account.address, idx)

                if func:
                    description += (
                        "There is a check on storage index " + str(idx) +
                        ". This storage slot can be written to by calling the function `"
                        + func + "`.\n")
                    interesting = True
                else:
                    logging.debug("[ETHER_SEND] No storage writes to index " +
                                  str(idx))

        if interesting:

            node = call.node

            can_solve = True
            constrained = False

            index = 0

            while can_solve and index < len(node.constraints):

                constraint = node.constraints[index]
                index += 1
                logging.debug("[ETHER_SEND] Constraint: " + str(constraint))

                m = re.search(r"storage_([a-z0-9_&^]+)", str(constraint))

                if m:

                    constrained = True
                    idx = m.group(1)

                    func = statespace.find_storage_write(
                        state.environment.active_account.address, idx)

                    if func:
                        description += (
                            "\nThere is a check on storage index " + str(idx) +
                            ". This storage slot can be written to by calling the function `"
                            + func + "`.")
                    else:
                        logging.debug(
                            "[ETHER_SEND] No storage writes to index " +
                            str(idx))
                        can_solve = False
                        break

                # CALLER may also be constrained to hardcoded address. I.e. 'caller' and some integer

                elif re.search(r"caller", str(constraint)) and re.search(
                        r"[0-9]{20}", str(constraint)):
                    constrained = True
                    can_solve = False
                    break

            if not constrained:
                description += (
                    "It seems that this function can be called without restrictions."
                )

            if can_solve:

                try:
                    model = solver.get_model(node.constraints)

                    pretty_model = solver.pretty_print_model(model)
                    logging.debug("[ETHER_SEND]\n" + pretty_model)

                    debug = "SOLVER OUTPUT:\n" + pretty_model

                    issue = Issue(
                        contract=call.node.contract_name,
                        function=call.node.function_name,
                        address=address,
                        title="Ether send",
                        _type="Warning",
                        swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
                        description=description,
                        debug=debug,
                    )
                    issues.append(issue)

                except UnsatError:
                    logging.debug("[ETHER_SEND] no model found")

    return issues
Exemple #12
0
def execute(statespace):

    logging.debug("Executing module: UNCHECKED_SUICIDE")

    issues = []

    for k in statespace.nodes:
        node = statespace.nodes[k]

        for state in node.states:

            instruction = state.get_current_instruction()

            if (instruction['opcode'] == "SUICIDE"):

                logging.debug("[UNCHECKED_SUICIDE] suicide in function " +
                              node.function_name)

                description = "The function `" + node.function_name + "` executes the SUICIDE instruction. "

                stack = copy.deepcopy(state.mstate.stack)
                to = stack.pop()

                if ("caller" in str(to)):
                    description += "The remaining Ether is sent to the caller's address.\n"
                elif ("storage" in str(to)):
                    description += "The remaining Ether is sent to a stored address.\n"
                elif ("calldata" in str(to)):
                    description += "The remaining Ether is sent to an address provided as a function argument.\n"
                elif (type(to) == BitVecNumRef):
                    description += "The remaining Ether is sent to: " + hex(
                        to.as_long()) + "\n"
                else:
                    description += "The remaining Ether is sent to: " + str(
                        to) + "\n"

                constrained = False
                can_solve = True

                index = 0

                while (can_solve and index < len(node.constraints)):

                    constraint = node.constraints[index]
                    index += 1

                    m = re.search(r'storage_([a-z0-9_&^]+)', str(constraint))

                    if (m):
                        constrained = True
                        idx = m.group(1)

                        logging.debug("STORAGE CONSTRAINT FOUND: " + idx)

                        func = statespace.find_storage_write(
                            state.environment.active_account.address, idx)

                        if func:
                            description += "\nThere is a check on storage index " + str(
                                idx
                            ) + ". This storage index can be written to by calling the function '" + func + "'."
                            break
                        else:
                            logging.debug(
                                "[UNCHECKED_SUICIDE] No storage writes to index "
                                + str(idx))
                            can_solve = False
                            break

                    elif (re.search(r"caller", str(constraint))
                          and re.search(r'[0-9]{20}', str(constraint))):
                        can_solve = False
                        break

                if not constrained:
                    description += "\nIt seems that this function can be called without restrictions."

                if can_solve:

                    try:

                        model = solver.get_model(node.constraints)

                        debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(
                            model)

                        issue = Issue(node.contract_name, node.function_name,
                                      instruction['address'],
                                      "Unchecked SUICIDE", "Warning",
                                      description, debug)
                        issues.append(issue)

                    except UnsatError:
                        logging.debug("[UNCHECKED_SUICIDE] no model found")

    return issues
Exemple #13
0
def execute(statespace):

    logging.debug("Executing module: ETHER_SEND")

    issues = []

    for call in statespace.calls:

        state = call.state
        address = state.get_current_instruction()['address']

        if ("callvalue" in str(call.value)):
            logging.debug("[ETHER_SEND] Skipping refund function")
            continue

        # We're only interested in calls that send Ether

        if call.value.type == VarType.CONCRETE:
            if call.value.val == 0:
                continue

        interesting = False

        description = "In the function '" + call.node.function_name + "' "

        if re.search(r'caller', str(call.to)):
            description += "a non-zero amount of Ether is sent to msg.sender.\n"
            interesting = True

        elif re.search(r'calldata', str(call.to)):
            description += "a non-zero amount of Ether is sent to an address taken from function arguments.\n"
            interesting = True

        else:
            m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to))

            if (m):
                idx = m.group(1)

                description += "a non-zero amount of Ether is sent to an address taken from storage slot " + str(idx) + ".\n"

                func = statespace.find_storage_write(state.environment.active_account.address, idx)

                if (func):
                    description += "There is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function '" + func + "'.\n"
                    interesting = True
                else:
                    logging.debug("[ETHER_SEND] No storage writes to index " + str(idx))

        if interesting:

            node = call.node

            can_solve = True
            constrained = False

            index = 0

            while(can_solve and index < len(node.constraints)):

                constraint = node.constraints[index]
                index += 1
                logging.debug("[ETHER_SEND] Constraint: " + str(constraint))

                m = re.search(r'storage_([a-z0-9_&^]+)', str(constraint))

                if (m):

                    constrained = True
                    idx = m.group(1)

                    func = statespace.find_storage_write(state.environment.active_account.address, idx)

                    if (func):
                        description += "\nThere is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function '" + func + "'."
                    else:
                        logging.debug("[ETHER_SEND] No storage writes to index " + str(idx))
                        can_solve = False
                        break

                # CALLER may also be constrained to hardcoded address. I.e. 'caller' and some integer

                elif (re.search(r"caller", str(constraint)) and re.search(r'[0-9]{20}', str(constraint))):
                    constrained = True
                    can_solve = False
                    break

            if not constrained:
                description += "It seems that this function can be called without restrictions."

            if can_solve:

                try:
                    model = solver.get_model(node.constraints)

                    for d in model.decls():
                        logging.debug("[ETHER_SEND] main model: %s = 0x%x" % (d.name(), model[d].as_long()))

                    debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model)

                    issue = Issue(call.node.contract_name, call.node.function_name, address, "Ether send", "Warning", description, debug)
                    issues.append(issue)

                except UnsatError:
                    logging.debug("[ETHER_SEND] no model found")

    return issues