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
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
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
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
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
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
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
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
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
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
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