51456 sc high token creator can revoke the upgrader role from the factory in order to avoid upgrades
Report ID: #51456
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenFactory.sol
Impacts: Contract fails to deliver promised returns, but doesn't lose value
Brief / Intro
Vulnerability Details
When a token is created, the factory assigns multiple roles to the token creator and assigns the UPGRADER_ROLE to the factory (address(this)):
token.grantRole(token.DEFAULT_ADMIN_ROLE(), msg.sender);
token.grantRole(token.ADMIN_ROLE(), msg.sender);
token.grantRole(token.MANAGER_ROLE(), msg.sender);
token.grantRole(token.YIELD_MANAGER_ROLE(), msg.sender);
token.grantRole(token.YIELD_DISTRIBUTOR_ROLE(), msg.sender);
token.grantRole(token.MINTER_ROLE(), msg.sender);
token.grantRole(token.BURNER_ROLE(), msg.sender);
token.grantRole(token.UPGRADER_ROLE(), address(this));Because the token creator is granted DEFAULT_ADMIN_ROLE, they can revoke or grant any role, including revoking the UPGRADER_ROLE from the factory. The token enforces upgrades via UUPS and requires UPGRADER_ROLE:
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) { }The factory exposes an admin-only upgrade function that expects to be able to upgrade tokens it created:
function upgradeToken(address token, address newImplementation) external onlyRole(DEFAULT_ADMIN_ROLE) {
FactoryStorage storage fs = _getFactoryStorage();
// Ensure the token was created by this factory
if (fs.tokenToImplementation[token] == address(0)) {
revert TokenNotCreatedByFactory();
}
// Ensure the new implementation is whitelisted
bytes32 codeHash = _getCodeHash(newImplementation);
if (!fs.allowedImplementations[codeHash]) {
revert ImplementationNotWhitelisted();
}
// Perform the upgrade (this assumes the token implements UUPSUpgradeable)
UUPSUpgradeable(token).upgradeToAndCall(newImplementation, "");
// Update the implementation mapping
fs.tokenToImplementation[token] = newImplementation;
emit TokenUpgraded(token, newImplementation);
}If the token creator revokes UPGRADER_ROLE from the factory, the factory's call to UUPSUpgradeable(token).upgradeToAndCall will revert because _authorizeUpgrade will fail the onlyRole(UPGRADER_ROLE) check. Conversely, the token owner (having DEFAULT_ADMIN_ROLE) can grant themself UPGRADER_ROLE and upgrade the token implementation arbitrarily.
Impact Details
The ArcTokenFactory will be unable to upgrade tokens it created if the token creator revokes the UPGRADER_ROLE from the factory.
Token creators could upgrade their token to an arbitrary implementation by granting themselves UPGRADER_ROLE, which may break intended guarantees or introduce malicious logic.
Overall: intended upgradeability and central admin control can be lost or subverted by token creators who hold DEFAULT_ADMIN_ROLE.
Proof of Concept
User creates a token by calling createToken on ArcTokenFactory.
The factory grants the user roles including DEFAULT_ADMIN_ROLE.
The factory also grants itself the UPGRADER_ROLE to allow upgrades.
The token creator revokes every role that was granted to the ArcTokenFactory (including UPGRADER_ROLE).
The factory (admin) can no longer upgrade the token using upgradeToken because _authorizeUpgrade requires UPGRADER_ROLE.
The token creator can grant themself UPGRADER_ROLE and upgrade the token implementation to any address.
References
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcTokenFactory.sol#L144
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcTokenFactory.sol#L261
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L648
Was this helpful?