Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions slither/core/declarations/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down