Inconsistencies in signers' and roles' management with implementation of access control
Description
To execute the necessary transactions, the contract owner has the ability to assign accounts to one of two roles, Minter or Redeemer, with the registerAllowedRole
function. This function sets the trusted signer address for the account and increments the _roleActiveSignerCount
.
function registerAllowedRole(
Role role,
address account,
address signer,
bool allowed
) external override nonReentrant onlyOwner {
...
_roleRegistry[role][account][signer] = allowed;
_roleActiveSignerCount[role][account] += 1;
...
}
Further, this value _roleActiveSignerCount
is used to validate whether the account is assigned the appropriate role. To pass the verification, the account must have at least one confirmed signer.
function isMinter(address minter) public view returns (bool) {
return _roleActiveSignerCount[Role.Minter][minter] >= 1;
}
Accounts with a role can also add their own trusted signer
accounts using the registerAllowedSigner
function and remove them with the removeRole
function, which deletes the _roleRegistry[role][msg.sender][signer]
and decrements _roleActiveSignerCount
for the msg.sender
.
function registerAllowedSigner(
Role role,
address signer,
bool allowed
) external override nonReentrant onlyRoleOrOwner(role) {
if (signer == address(0)) revert InvalidAddress();
_roleRegistry[role][msg.sender][signer] = allowed;
_roleActiveSignerCount[role][msg.sender] += 1;
emit RoleRegistered(role, msg.sender, msg.sender, allowed);
}
function removeRole(Role role, address signer) public nonReentrant onlyRoleOrOwner(role) {
if (!_roleRegistry[role][msg.sender][signer]) revert InvalidRole();
delete _roleRegistry[role][msg.sender][signer];
_roleActiveSignerCount[role][msg.sender] -= 1;
emit RoleRemoved(role, signer);
}
Impact
Since functions registerAllowedRole
and registerAllowedSigner
do not have checks that the signer
has already been added to the account
, the _roleActiveSignerCount
can be incremented repeatedly without adding a new signer
. And in this case, during the removeRole
function call, the _roleActiveSignerCount
for the account
will never reach zero; as a result, the account will always be assigned with this role, even if the account does not have allowed signers.
In addition, the registerAllowedRole
and registerAllowedSigner
functions get the input bool allowed
argument, which determines whether the signer
address will be set as trusted. In case allowed
is equal to false
, the _roleActiveSignerCount
will still be increased, although the signer
address was not actually added. So it will lead to the incorrect counting of _roleActiveSignerCount
.
Recommendations
We recommend considering using the OpenZeppelin's AccessControl↗ module because it provides a secure, audited, and standards-compliant way to manage roles and permissions and reduces the potential security risks associated with creating a custom solution.
Remediation
Ethena Labs acknowledged this finding and implemented a fix in commit df19a0d3↗.