Partial deprivileging of the SafetyModule's manager
Description
In Governable, the function updatePauser
is used to update SafetyModule's pauser. However, this function does not have any checks, so pauser
could be set to cozySafetyModuleManager
, which has the role of CallerRole.MANAGER
.
In StateChanger, the function _getCallerRole
is used to check the role of the address. When who_
is pauser
, it returns CallerRole.PAUSER
even though who_
is the cozySafetyModuleManager
because of the if-else routine.
function updatePauser(address _newPauser) external {
if (msg.sender != owner && msg.sender != pauser) revert Unauthorized();
emit PauserUpdated(_newPauser);
pauser = _newPauser;
function _getCallerRole(address who_) internal view returns (CallerRole) {
CallerRole role_ = CallerRole.NONE;
if (who_ == owner) role_ = CallerRole.OWNER;
else if (who_ == pauser) role_ = CallerRole.PAUSER;
// If the caller is the Manager itself, authorization for the call is done
// in the Manager.
else if (who_ == address(cozySafetyModuleManager)) role_ = CallerRole.MANAGER;
return role_;
}
Impact
The role of CallerRole.MANAGER
has more permissions than CallerRole.PAUSER
. This means that the manager cannot access functions before updating pauser
to the other address.
Recommendations
We recommend adding a check that _newPauser
is cozySafetyModuleManager
to prevent the manager from being deprivileged.
Remediation
This issue has been acknowledged by Cozy Finance Inc., and a fix was implemented in commit d0112ef4↗.