crytic / crytic/slither

[Bug-Candidate]: State Variable Writes in Library Calls Inside Functions Are Not Tracked

Open
#2,598 0 comments 0 reactions 0 assignees View on GitHub
bug-candidate
Dominant language
Python
Stars
6.4k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

### Describe the issue:

I want to track the state variable writes in the function MinterRole._addMinter(address), but I found that it only correctly returns the state variable _minters that the function read.
![image](https://github.com/user-attachments/assets/1f580bdb-55f1-4a4b-9531-c31178850a4f)
![image](https://github.com/user-attachments/assets/ce0503a2-329e-4363-a720-366404f158f2)
Then I tried to insert the following code snippet in the [function.py](https://github.com/crytic/slither/blob/3befc968bcda024b9952aeff8b3a17fd427426de/slither/core/declarations/function.py#L1630) , and it was able to solve the problem, but I feel it's not elegant enough.
```python
+ # consider state variables written in library calls
+ from slither.slithir.operations import LibraryCall
+ lbc_nodes = [x for x in self.nodes if x.library_calls]
+ for node in lbc_nodes:
+ for ir in node.irs:
+ if not isinstance(ir, LibraryCall):
+ continue
+ for (param, arg) in zip(ir.function.parameters, ir.arguments):
+ if param in ir.function.variables_written and isinstance(arg, StateVariable):
+ if arg not in self._state_vars_written:
+ self._state_vars_written.append(arg)
```

### Code example to reproduce the issue:

**Library:**
```solidity
library Roles {
struct Role {
mapping (address => bool) bearer;
}

/**
* @dev Give an account access to this role.
*/
function add(Role storage role, address account) internal {
require(!has(role, account), "Roles: account already has role");
role.bearer[account] = true;
}

/**
* @dev Remove an account's access to this role.
*/
function remove(Role storage role, address account) internal {
require(has(role, account), "Roles: account does not have role");
role.bearer[account] = false;
}

/**
* @dev Check if an account has this role.
* @return bool
*/
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0), "Roles: account is the zero address");
return role.bearer[account];
}
}

```

**Contract:**
```solidity
pragma solidity ^0.5.0;

contract Context {
// Empty internal constructor, to prevent people from mistakenly deploying
// an instance of this contract, which should be used via inheritance.
constructor () internal { }
// solhint-disable-previous-line no-empty-blocks

function _msgSender() internal view returns (address payable) {
return msg.sender;
}

function _msgData() internal view returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}

contract MinterRole is Context {
using Roles for Roles.Role;

event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);

Roles.Role private _minters;

constructor () internal {
_addMinter(_msgSender());
}

modifier onlyMinter() {
require(isMinter(_msgSender()), "MinterRole: caller does not have the Minter role");
_;
}

function isMinter(address account) public view returns (bool) {
return _minters.has(account);
}

function addMinter(address account) public onlyMinter {
_addMinter(account);
}

function renounceMinter() public {
_removeMinter(_msgSender());
}

function _addMinter(address account) internal {
_minters.add(account);
emit MinterAdded(account);
}

function _removeMinter(address account) internal {
_minters.remove(account);
emit MinterRemoved(account);
}
}
```

### Version:

0.10.4

### Relevant log output:

_No response_

Contributor guide

Open the contributing guide

Research direction

Start in slither/core/declarations/function.py around the state-variable tracking logic, then inspect the LibraryCall operation and the library_calls and variables_written data used there. Reproduce the issue with the Roles library and MinterRole._addMinter(address), and verify that the function reports _minters as written through the library call without relying on the ad hoc snippet.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, solidity
Domain
devtools, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.