diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 09ccef7cd8..d21f22b14c 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -217,6 +217,7 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None: self._solidity_signature: Optional[str] = None self._signature_str: Optional[str] = None self._canonical_name: Optional[str] = None + self._is_returning_msg_sender: Optional[bool] = None self._is_protected: Optional[bool] = None self.compilation_unit: "SlitherCompilationUnit" = compilation_unit @@ -1528,6 +1529,84 @@ def get_summary( ) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str]]: pass + def is_returning_msg_sender(self) -> bool: + """ + Determine if the function returns `msg.sender` directly or through aliased address variables. + + This includes: + - Functions that explicitly returns `msg.sender` + - Functions that returns a variable which was directly or transitively assigned from `msg.sender` + + Covers: + - Direct returns: + ``` + return msg.sender + ``` + - Aliased returns: + ``` + address a = msg.sender; + return a; + ``` + + Does not cover : + - Returns via internal function calls, even if those functions return `msg.sender`: + ``` + function _getUser() internal view returns (address) { + return _msgSender(); // _msgSender() returns msg.sender + } + ``` + + Returns + (bool) + """ + from slither.core.solidity_types import ElementaryType + from slither.slithir.operations import Return, Assignment + + if self._is_returning_msg_sender is None: + + # Skip analysis if function doesn't return or doesn't return an address. + if not self.returns or not all( + ret.type == ElementaryType("address") for ret in self.returns + ): + self._is_returning_msg_sender = False + return False + + return_vars = [] + assignment_map = {} + + for node in self.nodes: + for ir in node.irs: + # Direct return of msg.sender + if isinstance(ir, Return): + ir_return_vars = [i.name for i in ir.values if hasattr(i, "name")] + if "msg.sender" in ir_return_vars: + self._is_returning_msg_sender = True + return True + return_vars.extend(ir_return_vars) + + # Track assignments where an address-typed variable is assigned. + # This helps trace msg.sender aliases through reassignments. + if ( + isinstance(ir, Assignment) + and ir.lvalue.type == ElementaryType("address") + and hasattr(ir.lvalue, "name") + and hasattr(ir.rvalue, "name") + ): + lval, rval = ir.lvalue.name, ir.rvalue.name + assignment_map[lval] = assignment_map.get(rval, rval) + + for var in return_vars: + if var not in assignment_map: + continue + var = assignment_map[var] + if var == "msg.sender": + self._is_returning_msg_sender = True + return True + + self._is_returning_msg_sender = False + + return self._is_returning_msg_sender + def is_protected(self) -> bool: """ Determine if the function is protected using a check on msg.sender