def _arbitrary_from(nodes: List[Node], results: List[Node]): """Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter.""" for node in nodes: for ir in node.irs: if (isinstance(ir, HighLevelCall) and isinstance(ir.function, Function) and ir.function.solidity_signature == "transferFrom(address,address,uint256)" and not (is_dependent( ir.arguments[0], SolidityVariableComposed("msg.sender"), node.function.contract, ) or is_dependent( ir.arguments[0], SolidityVariable("this"), node.function.contract, ))): results.append(ir.node) elif (isinstance(ir, LibraryCall) and ir.function.solidity_signature == "safeTransferFrom(address,address,address,uint256)" and not (is_dependent( ir.arguments[1], SolidityVariableComposed("msg.sender"), node.function.contract, ) or is_dependent( ir.arguments[1], SolidityVariable("this"), node.function.contract, ))): results.append(ir.node)
def can_reenter(self, callstack=None): """ Must be called after slithIR analysis pass For Solidity > 0.5, filter access to public variables and constant/pure/view For call to this. check if the destination can re-enter :param callstack: check for recursion :return: bool """ if self.is_static_call(): return False # If there is a call to itself # We can check that the function called is # reentrancy-safe if self.destination == SolidityVariable("this"): if isinstance(self.function, Variable): return False # In case of recursion, return False callstack = [] if callstack is None else callstack if self.function in callstack: return False callstack = callstack + [self.function] if self.function.can_reenter(callstack): return True if isinstance(self.destination, Variable): if not self.destination.is_reentrant: return False return True
def can_reenter(self, callstack=None): ''' Must be called after slithIR analysis pass For Solidity > 0.5, filter access to public variables and constant/pure/view For call to this. check if the destination can re-enter :param callstack: check for recursion :return: bool ''' # If solidity >0.5, STATICCALL is used if self.slither.solc_version and self.slither.solc_version >= '0.5.0': if isinstance(self.function, Function) and (self.function.view or self.function.pure): return False if isinstance(self.function, Variable): return False # If there is a call to itself # We can check that the function called is # reentrancy-safe if self.destination == SolidityVariable('this'): if isinstance(self.function, Variable): return False # In case of recursion, return False callstack = [] if callstack is None else callstack if self.function in callstack: return False callstack = callstack + [self.function] if self.function.can_reenter(callstack): return True return True
def contains_bad_PRNG_sources( func: Function, blockhash_ret_values: List[Variable]) -> List[Node]: """ Check if any node in function has a modulus operator and the first operand is dependent on block.timestamp, now or blockhash() Returns: (nodes) """ ret = set() # pylint: disable=too-many-nested-blocks for node in func.nodes: for ir in node.irs_ssa: if isinstance(ir, Binary) and ir.type == BinaryType.MODULO: if is_dependent_ssa( ir.variable_left, SolidityVariableComposed("block.timestamp"), func.contract) or is_dependent_ssa( ir.variable_left, SolidityVariable("now"), func.contract): ret.add(node) break for ret_val in blockhash_ret_values: if is_dependent_ssa(ir.variable_left, ret_val, func.contract): ret.add(node) break return list(ret)
def _timestamp(func: Function) -> List[Node]: ret = set() for node in func.nodes: if node.contains_require_or_assert(): for var in node.variables_read: if is_dependent(var, SolidityVariableComposed("block.timestamp"), func.contract): ret.add(node) if is_dependent(var, SolidityVariable("now"), func.contract): ret.add(node) for ir in node.irs: if isinstance(ir, Binary) and BinaryType.return_bool(ir.type): for var in ir.read: if is_dependent( var, SolidityVariableComposed("block.timestamp"), func.contract ): ret.add(node) if is_dependent(var, SolidityVariable("now"), func.contract): ret.add(node) return sorted(list(ret), key=lambda x: x.node_id)
class IncorrectStrictEquality(AbstractDetector): ARGUMENT = 'incorrect-equality' HELP = 'Dangerous strict equalities' IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-strict-equalities' WIKI_TITLE = 'Dangerous strict equalities' WIKI_DESCRIPTION = 'Use of strick equalities that can be easily manipulated by an attacker.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract Crowdsale{ function fund_reached() public returns(bool){ return this.balance == 100 ether; } ``` `Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. `Crowdsale` reaches 100 ether. Bob sends 0.1 ether. As a result, `fund_reached` is always false and the crowdsale never ends.''' WIKI_RECOMMENDATION = '''Don't use strict equality to determine if an account has enough ethers or tokens.''' sources_taint = [ SolidityVariable('now'), SolidityVariableComposed('block.number'), SolidityVariableComposed('block.timestamp') ] @staticmethod def is_direct_comparison(ir): return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL @staticmethod def is_any_tainted(variables, taints, function): return any([ is_dependent_ssa(var, taint, function.contract) for var in variables for taint in taints ]) def taint_balance_equalities(self, functions): taints = [] for func in functions: for node in func.nodes: for ir in node.irs_ssa: if isinstance(ir, Balance): taints.append(ir.lvalue) if isinstance(ir, HighLevelCall): #print(ir.function.full_name) if isinstance(ir.function, Function) and\ ir.function.full_name == 'balanceOf(address)': taints.append(ir.lvalue) if isinstance(ir.function, StateVariable) and\ isinstance(ir.function.type, MappingType) and\ ir.function.name == 'balanceOf' and\ ir.function.type.type_from == ElementaryType('address') and\ ir.function.type.type_to == ElementaryType('uint256'): taints.append(ir.lvalue) if isinstance(ir, Assignment): if ir.rvalue in self.sources_taint: taints.append(ir.lvalue) return taints # Retrieve all tainted (node, function) pairs def tainted_equality_nodes(self, funcs, taints): results = dict() taints += self.sources_taint for func in funcs: for node in func.nodes: for ir in node.irs_ssa: # Filter to only tainted equality (==) comparisons if self.is_direct_comparison(ir) and self.is_any_tainted( ir.used, taints, func): if func not in results: results[func] = [] results[func].append(node) return results def detect_strict_equality(self, contract): funcs = contract.all_functions_called + contract.modifiers # Taint all BALANCE accesses taints = self.taint_balance_equalities(funcs) # Accumulate tainted (node,function) pairs involved in strict equality (==) comparisons results = self.tainted_equality_nodes(funcs, taints) return results def detect(self): results = [] for c in self.slither.contracts_derived: ret = self.detect_strict_equality(c) info = '' # sort ret to get deterministic results ret = sorted(list(ret.items()), key=lambda x: x[0].name) for f, nodes in ret: info += "{}.{} ({}) uses a dangerous strict equality:\n".format( f.contract.name, f.name, f.source_mapping_str) # sort the nodes to get deterministic results nodes.sort(key=lambda x: x.node_id) for node in nodes: info += "\t- {}\n".format(str(node.expression)) json = self.generate_json_result(info) self.add_function_to_json(f, json) self.add_nodes_to_json(nodes, json) results.append(json) if info: self.log(info) return results
class IncorrectStrictEquality(AbstractDetector): ARGUMENT = "incorrect-equality" HELP = "Dangerous strict equalities" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH WIKI = ( "https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities" ) WIKI_TITLE = "Dangerous strict equalities" WIKI_DESCRIPTION = "Use of strict equalities that can be easily manipulated by an attacker." WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Crowdsale{ function fund_reached() public returns(bool){ return this.balance == 100 ether; } ``` `Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. `Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends.""" WIKI_RECOMMENDATION = ( """Don't use strict equality to determine if an account has enough Ether or tokens.""" ) sources_taint = [ SolidityVariable("now"), SolidityVariableComposed("block.number"), SolidityVariableComposed("block.timestamp"), ] @staticmethod def is_direct_comparison(ir): return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL @staticmethod def is_any_tainted(variables, taints, function) -> bool: return any((is_dependent_ssa(var, taint, function.contract) for var in variables for taint in taints)) def taint_balance_equalities(self, functions): taints = [] for func in functions: for node in func.nodes: for ir in node.irs_ssa: if isinstance(ir, Balance): taints.append(ir.lvalue) if isinstance(ir, HighLevelCall): # print(ir.function.full_name) if (isinstance(ir.function, Function) and ir.function.full_name == "balanceOf(address)"): taints.append(ir.lvalue) if (isinstance(ir.function, StateVariable) and isinstance(ir.function.type, MappingType) and ir.function.name == "balanceOf" and ir.function.type.type_from == ElementaryType("address") and ir.function.type.type_to == ElementaryType("uint256")): taints.append(ir.lvalue) if isinstance(ir, Assignment): if ir.rvalue in self.sources_taint: taints.append(ir.lvalue) return taints # Retrieve all tainted (node, function) pairs def tainted_equality_nodes(self, funcs, taints): results = dict() taints += self.sources_taint for func in funcs: for node in func.nodes: for ir in node.irs_ssa: # Filter to only tainted equality (==) comparisons if self.is_direct_comparison(ir) and self.is_any_tainted( ir.used, taints, func): if func not in results: results[func] = [] results[func].append(node) return results def detect_strict_equality(self, contract): funcs = contract.all_functions_called + contract.modifiers # Taint all BALANCE accesses taints = self.taint_balance_equalities(funcs) # Accumulate tainted (node,function) pairs involved in strict equality (==) comparisons results = self.tainted_equality_nodes(funcs, taints) return results def _detect(self): results = [] for c in self.compilation_unit.contracts_derived: ret = self.detect_strict_equality(c) # sort ret to get deterministic results ret = sorted(list(ret.items()), key=lambda x: x[0].name) for f, nodes in ret: func_info = [f, " uses a dangerous strict equality:\n"] # sort the nodes to get deterministic results nodes.sort(key=lambda x: x.node_id) # Output each node with the function info header as a separate result. for node in nodes: node_info = func_info + ["\t- ", node, "\n"] res = self.generate_result(node_info) results.append(res) return results
def find_variable(var_name, caller_context): if isinstance(caller_context, Contract): function = None contract = caller_context elif isinstance(caller_context, Function): function = caller_context contract = function.contract else: logger.error('Incorrect caller context') exit(-1) if function: func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] contract_variables = contract.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = {get_pointer_name(f): f for f in contract.variables} if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] functions = contract.functions_as_dict() if var_name in functions: return functions[var_name] modifiers = contract.modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] structures = contract.structures_as_dict() if var_name in structures: return structures[var_name] events = contract.events_as_dict() if var_name in events: return events[var_name] enums = contract.enums_as_dict() if var_name in enums: return enums[var_name] # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict() for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] raise VariableNotFound('Variable not found: {}'.format(var_name))
def _find_read_write_call(self): # pylint: disable=too-many-statements for ir in self.irs: self._slithir_vars |= { v for v in ir.read if self._is_valid_slithir_var(v) } if isinstance(ir, OperationWithLValue): var = ir.lvalue if var and self._is_valid_slithir_var(var): self._slithir_vars.add(var) if not isinstance(ir, (Phi, Index, Member)): self._vars_read += [ v for v in ir.read if self._is_non_slithir_var(v) ] for var in ir.read: if isinstance(var, ReferenceVariable): self._vars_read.append(var.points_to_origin) elif isinstance(ir, (Member, Index)): var = ir.variable_left if isinstance( ir, Member) else ir.variable_right if self._is_non_slithir_var(var): self._vars_read.append(var) if isinstance(var, ReferenceVariable): origin = var.points_to_origin if self._is_non_slithir_var(origin): self._vars_read.append(origin) if isinstance(ir, OperationWithLValue): if isinstance(ir, (Index, Member, Length)): continue # Don't consider Member and Index operations -> ReferenceVariable var = ir.lvalue if isinstance(var, ReferenceVariable): var = var.points_to_origin if var and self._is_non_slithir_var(var): self._vars_written.append(var) if isinstance(ir, InternalCall): self._internal_calls.append(ir.function) if isinstance(ir, SolidityCall): # TODO: consider removing dependancy of solidity_call to internal_call self._solidity_calls.append(ir.function) self._internal_calls.append(ir.function) if isinstance(ir, LowLevelCall): assert isinstance(ir.destination, (Variable, SolidityVariable)) self._low_level_calls.append( (ir.destination, ir.function_name.value)) elif isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall): if isinstance(ir.destination.type, Contract): self._high_level_calls.append( (ir.destination.type, ir.function)) elif ir.destination == SolidityVariable("this"): self._high_level_calls.append( (self.function.contract, ir.function)) else: try: self._high_level_calls.append( (ir.destination.type.type, ir.function)) except AttributeError as error: # pylint: disable=raise-missing-from raise SlitherException( f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping_str})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}" ) elif isinstance(ir, LibraryCall): assert isinstance(ir.destination, Contract) self._high_level_calls.append((ir.destination, ir.function)) self._library_calls.append((ir.destination, ir.function)) self._vars_read = list(set(self._vars_read)) self._state_vars_read = [ v for v in self._vars_read if isinstance(v, StateVariable) ] self._local_vars_read = [ v for v in self._vars_read if isinstance(v, LocalVariable) ] self._solidity_vars_read = [ v for v in self._vars_read if isinstance(v, SolidityVariable) ] self._vars_written = list(set(self._vars_written)) self._state_vars_written = [ v for v in self._vars_written if isinstance(v, StateVariable) ] self._local_vars_written = [ v for v in self._vars_written if isinstance(v, LocalVariable) ] self._internal_calls = list(set(self._internal_calls)) self._solidity_calls = list(set(self._solidity_calls)) self._high_level_calls = list(set(self._high_level_calls)) self._library_calls = list(set(self._library_calls)) self._low_level_calls = list(set(self._low_level_calls))
def find_variable(var_name, caller_context, referenced_declaration=None, is_super=False): # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed if isinstance(caller_context, Contract): function = None contract = caller_context contract_declarer = caller_context elif isinstance(caller_context, Function): function = caller_context contract = function.contract contract_declarer = function.contract_declarer else: raise ParsingError('Incorrect caller context') if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration] # If not found, check for name func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] # variable are looked from the contract declarer contract_variables = contract_declarer.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = { get_pointer_name(f): f for f in contract_declarer.variables } if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: getter_available = lambda f: f.functions_declared d = {f.canonical_name: f for f in contract.functions} functions = { f.full_name: f for f in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: functions = contract.available_functions_as_dict() if var_name in functions: return functions[var_name] if is_super: getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} modifiers = { m.full_name: m for m in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] # structures are looked on the contract declarer structures = contract.structures_as_dict() if var_name in structures: return structures[var_name] events = contract.events_as_dict() if var_name in events: return events[var_name] enums = contract.enums_as_dict() if var_name in enums: return enums[var_name] # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict() for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] if referenced_declaration: for contract in contract.slither.contracts: if contract.id == referenced_declaration: return contract for function in contract.slither.functions: if function.referenced_declaration == referenced_declaration: return function raise VariableNotFound('Variable not found: {} (context {})'.format( var_name, caller_context))
def _find_read_write_call(self): for ir in self.irs: self._slithir_vars |= set([v for v in ir.read if self._is_valid_slithir_var(v)]) if isinstance(ir, OperationWithLValue): var = ir.lvalue if var and self._is_valid_slithir_var(var): self._slithir_vars.add(var) if not isinstance(ir, (Phi, Index, Member)): self._vars_read += [v for v in ir.read if self._is_non_slithir_var(v)] for var in ir.read: if isinstance(var, (ReferenceVariable)): self._vars_read.append(var.points_to_origin) elif isinstance(ir, (Member, Index)): var = ir.variable_left if isinstance(ir, Member) else ir.variable_right if self._is_non_slithir_var(var): self._vars_read.append(var) if isinstance(var, (ReferenceVariable)): origin = var.points_to_origin if self._is_non_slithir_var(origin): self._vars_read.append(origin) if isinstance(ir, OperationWithLValue): if isinstance(ir, (Index, Member, Length, Balance)): continue # Don't consider Member and Index operations -> ReferenceVariable var = ir.lvalue if isinstance(var, (ReferenceVariable)): var = var.points_to_origin if var and self._is_non_slithir_var(var): self._vars_written.append(var) if isinstance(ir, InternalCall): self._internal_calls.append(ir.function) if isinstance(ir, SolidityCall): # TODO: consider removing dependancy of solidity_call to internal_call self._solidity_calls.append(ir.function) self._internal_calls.append(ir.function) if isinstance(ir, LowLevelCall): assert isinstance(ir.destination, (Variable, SolidityVariable)) self._low_level_calls.append((ir.destination, ir.function_name.value)) elif isinstance(ir, (HighLevelCall)) and not isinstance(ir, LibraryCall): if isinstance(ir.destination.type, Contract): self._high_level_calls.append((ir.destination.type, ir.function)) elif ir.destination == SolidityVariable('this'): self._high_level_calls.append((self.function.contract, ir.function)) else: try: self._high_level_calls.append((ir.destination.type.type, ir.function)) except AttributeError: raise SlitherException(f'Function not found on {ir}. Please try compiling with a recent Solidity version.') elif isinstance(ir, LibraryCall): assert isinstance(ir.destination, Contract) self._high_level_calls.append((ir.destination, ir.function)) self._library_calls.append((ir.destination, ir.function)) self._vars_read = list(set(self._vars_read)) self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)] self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)] self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)] self._vars_written = list(set(self._vars_written)) self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)] self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)] self._internal_calls = list(set(self._internal_calls)) self._solidity_calls = list(set(self._solidity_calls)) self._high_level_calls = list(set(self._high_level_calls)) self._library_calls = list(set(self._library_calls)) self._low_level_calls = list(set(self._low_level_calls))
def find_variable( var_name: str, caller_context: CallerContext, referenced_declaration: Optional[int] = None, is_super=False, ) -> Union[ Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure, ]: from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.contract import ContractSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context) all_contracts = sl.contracts all_functions_parser = sl_parser.all_functions_and_modifiers_parser # Look for all references delcaration # First look only in the context of function/contract # Then look everywhere # Because functions are copied between contracts, two functions can have the same ref # So we need to first look with respect to the direct context ret = _find_variable_from_ref_declaration( referenced_declaration, direct_contracts, direct_functions_parser ) if ret: return ret ret = _find_variable_from_ref_declaration( referenced_declaration, all_contracts, all_functions_parser ) if ret: return ret function_parser: Optional[FunctionSolc] = ( caller_context if isinstance(caller_context, FunctionSolc) else None ) ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration) if ret: return ret contract: Optional[Contract] = None contract_declarer: Optional[Contract] = None if isinstance(caller_context, ContractSolc): contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): underlying_func = caller_context.underlying_function # If contract_parser is set to None, then underlying_function is a functionContract assert isinstance(underlying_func, FunctionContract) contract = underlying_func.contract contract_declarer = underlying_func.contract_declarer ret = _find_in_contract(var_name, contract, contract_declarer, is_super) if ret: return ret # Could refer to any enum all_enumss = [c.enums_as_dict for c in sl.contracts] all_enums = {k: v for d in all_enumss for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] contracts = sl.contracts_as_dict if var_name in contracts: return contracts[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) # Top level must be at the end, if nothing else was found ret = _find_top_level(var_name, sl) if ret: return ret raise VariableNotFound("Variable not found: {} (context {})".format(var_name, caller_context))
def find_variable( var_name: str, caller_context: CallerContext, referenced_declaration: Optional[int] = None, is_super=False, ) -> Union[Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure]: from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed if isinstance(caller_context, ContractSolc): function: Optional[FunctionSolc] = None contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): function = caller_context contract = function.underlying_function.contract contract_declarer = function.underlying_function.contract_declarer else: raise ParsingError("Incorrect caller context") if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration].underlying_variable # If not found, check for name func_variables = function.underlying_function.variables_as_dict if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.underlying_function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] # variable are looked from the contract declarer contract_variables = contract_declarer.variables_as_dict if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = { get_pointer_name(f): f for f in contract_declarer.variables } if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: getter_available = lambda f: f.functions_declared d = {f.canonical_name: f for f in contract.functions} functions = { f.full_name: f for f in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: functions = contract.available_functions_as_dict() if var_name in functions: return functions[var_name] if is_super: getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} modifiers = { m.full_name: m for m in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] # structures are looked on the contract declarer structures = contract.structures_as_dict if var_name in structures: return structures[var_name] structures_top_level = contract.slither.top_level_structures for st in structures_top_level: if st.name == var_name: return st events = contract.events_as_dict if var_name in events: return events[var_name] enums = contract.enums_as_dict if var_name in enums: return enums[var_name] enums_top_level = contract.slither.top_level_enums for enum in enums_top_level: if enum.name == var_name: return enum # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict if var_name in contracts: return contracts[var_name] if referenced_declaration: # id of the contracts is the referenced declaration # This is not true for the functions, as we dont always have the referenced_declaration # But maybe we could? (TODO) for contract_candidate in contract.slither.contracts: if contract_candidate.id == referenced_declaration: return contract_candidate for function_candidate in caller_context.slither_parser.all_functions_parser: if function_candidate.referenced_declaration == referenced_declaration: return function_candidate.underlying_function raise VariableNotFound("Variable not found: {} (context {})".format( var_name, caller_context))
def find_variable( var_name: str, caller_context: CallerContextExpression, referenced_declaration: Optional[int] = None, is_super: bool = False, ) -> Tuple[ Union[ Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure, CustomError, TypeAlias, ], bool, ]: """ Return the variable found and a boolean indicating if the variable was created If the variable was created, it has no source mapping, and it the caller must add it :param var_name: :type var_name: :param caller_context: :type caller_context: :param referenced_declaration: :type referenced_declaration: :param is_super: :type is_super: :return: :rtype: """ from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.contract import ContractSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed direct_contracts, direct_functions, current_scope = _find_variable_init(caller_context) # Only look for reference declaration in the direct contract, see comment at the end # Reference looked are split between direct and all # Because functions are copied between contracts, two functions can have the same ref # So we need to first look with respect to the direct context if var_name in current_scope.renaming: var_name = current_scope.renaming[var_name] if var_name in current_scope.user_defined_types: return current_scope.user_defined_types[var_name], False # Use ret0/ret1 to help mypy ret0 = _find_variable_from_ref_declaration( referenced_declaration, direct_contracts, direct_functions ) if ret0: return ret0, False function_parser: Optional[FunctionSolc] = ( caller_context if isinstance(caller_context, FunctionSolc) else None ) ret1 = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration) if ret1: return ret1, False contract: Optional[Contract] = None contract_declarer: Optional[Contract] = None if isinstance(caller_context, ContractSolc): contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): underlying_func = caller_context.underlying_function if isinstance(underlying_func, FunctionContract): contract = underlying_func.contract contract_declarer = underlying_func.contract_declarer else: assert isinstance(underlying_func, FunctionTopLevel) ret = _find_in_contract(var_name, contract, contract_declarer, is_super) if ret: return ret, False # Could refer to any enum all_enumss = [c.enums_as_dict for c in current_scope.contracts.values()] all_enums = {k: v for d in all_enumss for k, v in d.items()} if var_name in all_enums: return all_enums[var_name], False contracts = current_scope.contracts if var_name in contracts: return contracts[var_name], False if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name), False if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name), False # Top level must be at the end, if nothing else was found ret, var_was_created = _find_top_level(var_name, current_scope) if ret: return ret, var_was_created # Look from reference declaration in all the contracts at the end # Because they are many instances where this can't be trusted # For example in # contract A{ # function _f() internal view returns(uint){ # return 1; # } # # function get() public view returns(uint){ # return _f(); # } # } # # contract B is A{ # function _f() internal view returns(uint){ # return 2; # } # # } # get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not ret = _find_variable_from_ref_declaration( referenced_declaration, list(current_scope.contracts.values()), list(current_scope.functions), ) if ret: return ret, False raise VariableNotFound(f"Variable not found: {var_name} (context {contract})")
class IncorrectStrictEquality(AbstractDetector): ARGUMENT = 'incorrect-equality' HELP = 'Dangerous strict equalities' IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-strict-equalities' sources_taint = [SolidityVariable('now'), SolidityVariableComposed('block.number'), SolidityVariableComposed('block.timestamp')] @staticmethod def is_direct_comparison(ir): return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL @staticmethod def is_any_tainted(variables, taints, function): return any([is_dependent_ssa(var, taint, function.contract) for var in variables for taint in taints]) def taint_balance_equalities(self, functions): taints = [] for func in functions: for node in func.nodes: for ir in node.irs_ssa: if isinstance(ir, Balance): taints.append(ir.lvalue) if isinstance(ir, HighLevelCall): #print(ir.function.full_name) if isinstance(ir.function, Function) and\ ir.function.full_name == 'balanceOf(address)': taints.append(ir.lvalue) if isinstance(ir.function, StateVariable) and\ isinstance(ir.function.type, MappingType) and\ ir.function.name == 'balanceOf' and\ ir.function.type.type_from == ElementaryType('address') and\ ir.function.type.type_to == ElementaryType('uint256'): taints.append(ir.lvalue) if isinstance(ir, Assignment): if ir.rvalue in self.sources_taint: taints.append(ir.lvalue) return taints # Retrieve all tainted (node, function) pairs def tainted_equality_nodes(self, funcs, taints): results = dict() taints += self.sources_taint for func in funcs: for node in func.nodes: for ir in node.irs_ssa: # Filter to only tainted equality (==) comparisons if self.is_direct_comparison(ir) and self.is_any_tainted(ir.used, taints, func): if func not in results: results[func] = [] results[func].append(node) return results def detect_strict_equality(self, contract): funcs = contract.all_functions_called + contract.modifiers # Taint all BALANCE accesses taints = self.taint_balance_equalities(funcs) # Accumulate tainted (node,function) pairs involved in strict equality (==) comparisons results = self.tainted_equality_nodes(funcs, taints) return results def detect(self): results = [] for c in self.slither.contracts_derived: ret = self.detect_strict_equality(c) info = '' # sort ret to get deterministic results ret = sorted(list(ret.items()), key=lambda x:x[0].name) for f, nodes in ret: info += "{}.{} ({}) uses a dangerous strict equality:\n".format(f.contract.name, f.name, f.source_mapping_str) # sort the nodes to get deterministic results nodes.sort(key=lambda x: x.node_id) for node in nodes: info += "\t- {}\n".format(str(node.expression)) json = self.generate_json_result(info) self.add_function_to_json(f, json) self.add_nodes_to_json(nodes, json) results.append(json) if info: self.log(info) return results