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

Since the token creator is granted the DEFAULT_ADMIN_ROLE, the token creator can revoke or grant any role. This allows the token creator to remove the UPGRADER_ROLE from the factory (or grant it to themselves), preventing the factory from performing intended upgrades or enabling arbitrary upgrades by the token owner.

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

1

User creates a token by calling createToken on ArcTokenFactory.

2

The factory grants the user roles including DEFAULT_ADMIN_ROLE.

3

The factory also grants itself the UPGRADER_ROLE to allow upgrades.

4

The token creator revokes every role that was granted to the ArcTokenFactory (including UPGRADER_ROLE).

5

The factory (admin) can no longer upgrade the token using upgradeToken because _authorizeUpgrade requires UPGRADER_ROLE.

6

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?