def execute(statespace): logging.debug("Executing module: INTEGER_UNDERFLOW") issues = [] for k in statespace.nodes: node = statespace.nodes[k] for instruction in node.instruction_list: if(instruction['opcode'] == "SUB"): stack = node.states[instruction['address']].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 possible (but apparently non-exploitable) Integer 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 # Both seem to be standard compiler outputs that exist in many contracts. continue logging.debug("[INTEGER_UNDERFLOW] Checking SUB " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address'])) constraints.append(UGT(op1,op0)) try: model = solver.get_model(constraints) issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Underflow", "Warning") issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \ "The SUB instruction at address " + str(instruction['address']) + " may result in a value < 0." issue.debug = "(" + str(op0) + ") - (" + str(op1) + ").]" issues.append(issue) for d in model.decls(): logging.debug("[INTEGER_UNDERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long())) except UnsatError: logging.debug("[INTEGER_UNDERFLOW] 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 _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 _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 _handle_sstore(self, state): stack = state.mstate.stack value = stack[-2] if not isinstance(value, Expression): return for annotation in value.annotations: if not isinstance(annotation, OverUnderflowAnnotation): continue _type = "Underflow" if annotation.operator == "subtraction" else "Overflow" ostate = annotation.overflowing_state node = ostate.node issue = Issue( contract=node.contract_name, function_name=node.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), ) 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: transaction_sequence = solver.get_transaction_sequence( state, node.constraints + [annotation.constraint]) issue.debug = json.dumps(transaction_sequence, indent=4) except UnsatError: continue if annotation.operator == "subtraction": self._underflow_cache[address] = True else: self._overflow_cache[address] = True self._issues.append(issue)
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 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 _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 # constraint = Not(BVAddNoOverflow(op0, op1, signed=False)) else: expr = op1 * op0 # constraint = Not(BVMulNoOverflow(op0, op1, signed=False)) constraint = Or(And(ULT(expr, op0), op1 != 0), And(ULT(expr, op1), op0 != 0)) # Check satisfiable 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( contract=node.contract_name, function_name=node.function_name, address=instruction["address"], swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, bytecode=state.environment.code.bytecode, title="Integer Overflow", _type="Warning", ) issue.description = "The arithmetic operation can result in integer overflow.\n" issue.debug = "Transaction Sequence: " + str( solver.get_transaction_sequence(state, node.constraints) ) issues.append(issue) return issues
def _check_integer_overflow(self, 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 # FIXME: handle exponentiation if instruction["opcode"] == "ADD": operator = "add" expr = op0 + op1 constraint = Not(BVAddNoOverflow(op0, op1, signed=False)) else: operator = "multiply" expr = op1 * op0 constraint = Not(BVMulNoOverflow(op0, op1, signed=False)) # Check satisfiable model = self._try_constraints(node.constraints, [constraint]) if model is None: logging.debug("[INTEGER_OVERFLOW] no model found") return issues # Build issue issue = Issue( contract=node.contract_name, function_name=node.function_name, address=instruction["address"], swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, bytecode=state.environment.code.bytecode, title="Integer Overflow", _type="Warning", gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = "This binary {} operation can result in integer overflow.\n".format( operator) try: issue.debug = "Transaction Sequence: " + str( solver.get_transaction_sequence(state, node.constraints)) except UnsatError: return issues issues.append(issue) return issues
def _check_integer_underflow(self, 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 = self._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_name=node.function_name, address=instruction["address"], swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, bytecode=state.environment.code.bytecode, title="Integer Underflow", _type="Warning", gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = ( "The subtraction can result in an integer underflow.\n" ) issue.debug = "Transaction Sequence: " + str( solver.get_transaction_sequence( state, node.constraints)) issues.append(issue) except UnsatError: logging.debug("[INTEGER_UNDERFLOW] no model found") return issues
def execute(statespace): issues = [] for k in statespace.nodes: node = statespace.nodes[k] for instruction in node.instruction_list: ''' This generates a lot of noise. if(instruction['opcode'] == "ADD"): stack = node.states[instruction['address']].stack op0 = stack[-1] op1 = stack[-2] if type(op0) == int and type(op1) == int: continue logging.debug("[INTEGER_OVERFLOW] Checking ADD " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address'])) constraints = copy.deepcopy(node.constraints) constraints.append(UGT(op0, UINT_MAX - op1)) try: model = solver.get_model(constraints) issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Overflow", "Warning") issue.description = "A possible integer overflow exists in the function " + node.function_name + ".\n" \ "The addition at address " + str(instruction['address']) + " may result in a value greater than UINT_MAX." issue.debug = "(" + str(op0) + ") + (" + str(op1) + ") > (" + hex(UINT_MAX.as_long()) + ")" issues.append(issue) for d in model.decls(): logging.debug("[INTEGER_OVERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long())) except UnsatError: logging.debug("[INTEGER_OVERFLOW] no model found") ''' if (instruction['opcode'] == "MUL"): stack = node.states[instruction['address']].stack op0 = stack[-1] op1 = stack[-2] if (type(op0) == int and type(op1) == int ) or type(op0) == BoolRef or type(op1) == BoolRef: continue logging.debug("[INTEGER_OVERFLOW] Checking MUL " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address'])) if re.search(r'146150163733', str(op0), re.DOTALL) or re.search( r'146150163733', str(op1), re.DOTALL) or "(2 << 160 - 1)" in str( op0) or "(2 << 160 - 1)" in str(op1): continue constraints = copy.deepcopy(node.constraints) constraints.append(UGT(op0, UDiv(UINT_MAX, op1))) try: model = solver.get_model(constraints) issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Overflow", "Warning") issue.description = "A possible integer overflow exists in the function " + node.function_name + ".\n" \ "The multiplication at address " + str(instruction['address']) + " may result in a value greater than UINT_MAX." issue.debug = "(" + str(op0) + ") * (" + str( op1) + ") > (" + hex(UINT_MAX.as_long()) + ")" issues.append(issue) logging.debug("Constraints: " + str(constraints)) for d in model.decls(): logging.debug("[INTEGER_OVERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long())) except UnsatError: logging.debug("[INTEGER_OVERFLOW] no model found") return issues
def _check_integer_underflow(self, 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) if type(op0) == int and type(op1) == int: 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( Not(BVSubNoUnderflow(op0, op1, signed=False))) 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 = self._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_name=node.function_name, address=instruction["address"], swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW, bytecode=state.environment.code.bytecode, title="Integer Underflow", _type="Warning", gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = ( "The subtraction can result in an integer underflow.\n" ) issue.debug = str( solver.get_transaction_sequence( state, node.constraints)) issues.append(issue) except UnsatError: logging.debug("[INTEGER_UNDERFLOW] no model found") return issues