def _handle_transaction_end(self, state: GlobalState) -> None: state_annotation = _get_overflowunderflow_state_annotation(state) for annotation in state_annotation.overflowing_state_annotations: ostate = annotation.overflowing_state if ostate in self._ostates_unsatisfiable: continue if ostate not in self._ostates_satisfiable: try: constraints = ostate.mstate.constraints + [ annotation.constraint ] solver.get_model(constraints) self._ostates_satisfiable.add(ostate) except: self._ostates_unsatisfiable.add(ostate) continue log.debug( "Checking overflow in {} at transaction end address {}, ostate address {}" .format( state.get_current_instruction()["opcode"], state.get_current_instruction()["address"], ostate.get_current_instruction()["address"], )) try: 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), transaction_sequence=transaction_sequence, ) address = _get_address_from_state(ostate) self.cache.add(address) self.issues.append(issue)
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 []
def get_issue(self, global_state: GlobalState) -> Optional[Issue]: if not self.state_change_states: return None severity = "Medium" if self.user_defined_address else "Low" address = global_state.get_current_instruction()["address"] logging.debug( "[EXTERNAL_CALLS] Detected state changes at addresses: {}".format( address)) description_head = ( "The contract account state is changed after an external call. ") description_tail = ( "Consider that the called contract could re-enter the function before this " "state change takes place. This can lead to business logic vulnerabilities." ) return Issue( contract=global_state.environment.active_account.contract_name, function_name=global_state.environment.active_function_name, address=address, title="State change after external call", severity=severity, description_head=description_head, description_tail=description_tail, swc_id=REENTRANCY, bytecode=global_state.environment.code.bytecode, )
def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord: assert len(state.mstate.stack) == len(record.stack) """ Runs taint analysis on a state """ record.add_state(state) new_record = record.clone() # Apply Change op = state.get_current_instruction()["opcode"] if op in TaintRunner.stack_taint_table.keys(): mutator = TaintRunner.stack_taint_table[op] TaintRunner.mutate_stack(new_record, mutator) elif op.startswith("PUSH"): TaintRunner.mutate_push(op, new_record) elif op.startswith("DUP"): TaintRunner.mutate_dup(op, new_record) elif op.startswith("SWAP"): TaintRunner.mutate_swap(op, new_record) elif op is "MLOAD": TaintRunner.mutate_mload(new_record, state.mstate.stack[-1]) elif op.startswith("MSTORE"): TaintRunner.mutate_mstore(new_record, state.mstate.stack[-1]) elif op is "SLOAD": TaintRunner.mutate_sload(new_record, state.mstate.stack[-1]) elif op is "SSTORE": TaintRunner.mutate_sstore(new_record, state.mstate.stack[-1]) elif op.startswith("LOG"): TaintRunner.mutate_log(new_record, op) elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): TaintRunner.mutate_call(new_record, op) else: logging.debug("Unknown operation encountered: {}".format(op)) return new_record
def jumpi_hook(state: GlobalState): address = state.get_current_instruction()["address"] annotation = get_dependency_annotation(state) annotation.path.append(address) _check_basic_block(address, annotation)
def _execute(self, state: GlobalState) -> None: if state.get_current_instruction()["address"] in self.cache: return issues = self._analyze_state(state) annotation = get_potential_issues_annotation(state) annotation.potential_issues.extend(issues)
def _execute(self, state: GlobalState) -> None: if state.get_current_instruction()["address"] in self._cache: return issues = self._analyze_state(state) for issue in issues: self._cache.add(issue.address) self._issues.extend(issues)
def world_state_filter_hook(state: GlobalState): if isinstance(state.current_transaction, ContractCreationTransaction): # Reset iteration variable self.iteration = 0 return world_state_annotation = get_ws_dependency_annotation(state) annotation = get_dependency_annotation(state) # Reset the state annotation except for storage written which is carried on to # the next transaction annotation.path = [0] annotation.storage_loaded = [] annotation.has_call = False world_state_annotation.annotations_stack.append(annotation) log.debug( "Iteration {}: Adding world state at address {}, end of function {}.\nDependency map: {}\nStorage written: {}" .format( self.iteration, state.get_current_instruction()["address"], state.node.function_name, self.dependency_map, annotation.storage_written[self.iteration], ))
def test_delegate_call(sym_mock, concrete_mock, curr_instruction): # arrange # sym_mock = mocker.patch.object(delegatecall, "_symbolic_call") # concrete_mock = mocker.patch.object(delegatecall, "_concrete_call") sym_mock.return_value = [] concrete_mock.return_value = [] curr_instruction.return_value = {"address": "0x10"} active_account = Account("0x10") active_account.code = Disassembly("00") environment = Environment(active_account, None, None, None, None, None) state = GlobalState(None, environment, Node) state.mstate.memory = ["placeholder", "calldata_bling_0"] state.mstate.stack = [1, 2, 3] assert state.get_current_instruction() == {"address": "0x10"} node = Node("example") node.contract_name = "the contract name" node.function_name = "fallback" to = Variable("storage_1", VarType.SYMBOLIC) call = Call(node, state, None, "DELEGATECALL", to, None) statespace = MagicMock() statespace.calls = [call] # act execute(statespace) # assert assert concrete_mock.call_count == 1 assert sym_mock.call_count == 1
def _execute(self, state: GlobalState) -> None: """Executes analysis module for integer underflow and integer overflow. :param state: Statespace to analyse :return: Found issues """ address = _get_address_from_state(state) if address in self.cache: return opcode = state.get_current_instruction()["opcode"] funcs = { "ADD": [self._handle_add], "SUB": [self._handle_sub], "MUL": [self._handle_mul], "SSTORE": [self._handle_sstore], "JUMPI": [self._handle_jumpi], "CALL": [self._handle_call], "RETURN": [self._handle_return, self._handle_transaction_end], "STOP": [self._handle_transaction_end], "EXP": [self._handle_exp], } for func in funcs[opcode]: func(state)
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
def __init__(self, call_state: GlobalState, constraints: List) -> None: """ Initialize DelegateCall Annotation :param call_state: Call state """ self.call_state = call_state self.constraints = constraints self.return_value = call_state.new_bitvec( "retval_{}".format(call_state.get_current_instruction()["address"]), 256 )
def execute(self, state: GlobalState): """Executes analysis module for integer underflow and integer overflow. :param state: Statespace to analyse :return: Found issues """ address = _get_address_from_state(state) has_overflow = self._overflow_cache.get(address, False) has_underflow = self._underflow_cache.get(address, False) if has_overflow or has_underflow: return if state.get_current_instruction()["opcode"] == "ADD": self._handle_add(state) elif state.get_current_instruction()["opcode"] == "MUL": self._handle_mul(state) elif state.get_current_instruction()["opcode"] == "SUB": self._handle_sub(state) elif state.get_current_instruction()["opcode"] == "SSTORE": self._handle_sstore(state) elif state.get_current_instruction()["opcode"] == "JUMPI": self._handle_jumpi(state)
def get_call_from_state(state: GlobalState) -> Union[Call, None]: """ :param state: :return: """ instruction = state.get_current_instruction() op = instruction["opcode"] stack = state.mstate.stack if op in ("CALL", "CALLCODE"): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), get_variable(stack[-7]), ) if to.type == VarType.CONCRETE and to.val < 5: return None if meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE: return Call( state.node, state, None, op, to, gas, value, state.mstate.memory[meminstart.val:meminsz.val * 4], ) else: return Call(state.node, state, None, op, to, gas, value) else: gas, to, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), ) return Call(state.node, state, None, op, to, gas)
def get_issue( self, global_state: GlobalState, detector: DetectionModule ) -> Optional[PotentialIssue]: if not self.state_change_states: return None constraints = Constraints() gas = self.call_state.mstate.stack[-1] to = self.call_state.mstate.stack[-2] constraints += [ UGT(gas, symbol_factory.BitVecVal(2300, 256)), Or( to > symbol_factory.BitVecVal(16, 256), to == symbol_factory.BitVecVal(0, 256), ), ] if self.user_defined_address: constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF] try: solver.get_transaction_sequence( global_state, constraints + global_state.mstate.constraints ) except UnsatError: return None severity = "Medium" if self.user_defined_address else "Low" address = global_state.get_current_instruction()["address"] logging.debug( "[EXTERNAL_CALLS] Detected state changes at addresses: {}".format(address) ) description_head = ( "The contract account state is changed after an external call. " ) description_tail = ( "Consider that the called contract could re-enter the function before this " "state change takes place. This can lead to business logic vulnerabilities." ) return PotentialIssue( contract=global_state.environment.active_account.contract_name, function_name=global_state.environment.active_function_name, address=address, title="State change after external call", severity=severity, description_head=description_head, description_tail=description_tail, swc_id=REENTRANCY, bytecode=global_state.environment.code.bytecode, constraints=constraints, detector=detector, )
def native_call( global_state: GlobalState, callee_address: Union[str, BitVec], call_data: BaseCalldata, memory_out_offset: Union[int, Expression], memory_out_size: Union[int, Expression], ) -> Optional[List[GlobalState]]: if ( isinstance(callee_address, BitVec) or not 0 < int(callee_address, 16) <= PRECOMPILE_COUNT ): return None log.debug("Native contract called: " + callee_address) try: mem_out_start = util.get_concrete_int(memory_out_offset) mem_out_sz = util.get_concrete_int(memory_out_size) except TypeError: log.debug("CALL with symbolic start or offset not supported") return [global_state] contract_list = ["ecrecover", "sha256", "ripemd160", "identity"] call_address_int = int(callee_address, 16) native_gas_min, native_gas_max = calculate_native_gas( global_state.mstate.calculate_extension_size(mem_out_start, mem_out_sz), contract_list[call_address_int - 1], ) global_state.mstate.min_gas_used += native_gas_min global_state.mstate.max_gas_used += native_gas_max global_state.mstate.mem_extend(mem_out_start, mem_out_sz) try: data = natives.native_contracts(call_address_int, call_data) except natives.NativeContractException: for i in range(mem_out_sz): global_state.mstate.memory[mem_out_start + i] = global_state.new_bitvec( contract_list[call_address_int - 1] + "(" + str(call_data) + ")", 8 ) return [global_state] for i in range( min(len(data), mem_out_sz) ): # If more data is used then it's chopped off global_state.mstate.memory[mem_out_start + i] = data[i] retval = global_state.new_bitvec( "retval_" + str(global_state.get_current_instruction()["address"]), 256 ) global_state.mstate.stack.append(retval) global_state.node.constraints.append(retval == 1) return [global_state]
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(self, state: GlobalState): """ :param state: :return: """ topic, size, mem_start = state.mstate.stack[-3:] if topic.symbolic or topic.value != assertion_failed_hash: return [] message = None if not mem_start.symbolic and not size.symbolic: message = eth_abi.decode_single( "string", bytes(state.mstate.memory[mem_start.value + 32:mem_start.value + size.value]), ).decode("utf8") description_head = "A user-provided assertion failed." if message: description_tail = "A user-provided assertion failed with message '{}'. Make sure the user-provided assertion is correct.".format( message) else: description_tail = "A user-provided assertion failed. Make sure the user-provided assertion is correct." address = state.get_current_instruction()["address"] issue = PotentialIssue( contract=state.environment.active_account.contract_name, function_name=state.environment.active_function_name, address=address, swc_id=ASSERT_VIOLATION, title="Assertion Failed", bytecode=state.environment.code.bytecode, severity="Medium", description_head=description_head, description_tail=description_tail, constraints=[], detector=self, ) return [issue]
def _analyze_states(state: GlobalState) -> List[Issue]: """ :param state: the current state :return: returns the issues for that corresponding state """ call = get_call_from_state(state) if call is None: return [] issues = [] # type: List[Issue] if call.type is not "DELEGATECALL": return [] if call.node.function_name is not "fallback": return [] state = call.state address = state.get_current_instruction()["address"] meminstart = get_variable(state.mstate.stack[-3]) if meminstart.type == VarType.CONCRETE: issues += _concrete_call(call, state, address, meminstart) return issues
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 []
def _analyze_state(self, state: GlobalState): """ :param state: :return: """ gas = state.mstate.stack[-1] to = state.mstate.stack[-2] address = state.get_current_instruction()["address"] try: constraints = Constraints( [UGT(gas, symbol_factory.BitVecVal(2300, 256))]) solver.get_transaction_sequence( state, constraints + state.mstate.constraints) # Check whether we can also set the callee address try: constraints += [to == ATTACKER_ADDRESS] for tx in state.world_state.transaction_sequence: if not isinstance(tx, ContractCreationTransaction): constraints.append(tx.caller == ATTACKER_ADDRESS) solver.get_transaction_sequence( state, constraints + state.mstate.constraints) description_head = "A call to a user-supplied address is executed." description_tail = ( "The callee address of an external message call can be set by " "the caller. Note that the callee can contain arbitrary code and may re-enter any function " "in this contract. Review the business logic carefully to prevent averse effects on the " "contract state.") issue = PotentialIssue( contract=state.environment.active_account.contract_name, function_name=state.environment.active_function_name, address=address, swc_id=REENTRANCY, title="External Call To User-Supplied Address", bytecode=state.environment.code.bytecode, severity="Medium", description_head=description_head, description_tail=description_tail, constraints=constraints, detector=self, ) except UnsatError: if _is_precompile_call(state): return [] log.debug( "[EXTERNAL_CALLS] Callee address cannot be modified. Reporting informational issue." ) description_head = "The contract executes an external message call." description_tail = ( "An external function call to a fixed contract address is executed. Make sure " "that the callee contract has been reviewed carefully.") issue = PotentialIssue( contract=state.environment.active_account.contract_name, function_name=state.environment.active_function_name, address=address, swc_id=REENTRANCY, title="External Call To Fixed Address", bytecode=state.environment.code.bytecode, severity="Low", description_head=description_head, description_tail=description_tail, constraints=constraints, detector=self, ) except UnsatError: log.debug("[EXTERNAL_CALLS] No model found.") return [] return [issue]
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
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 []
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 []
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 []