Summary
AviBridge.sol and AviERC721Bridge.sol track administrator count with a manual _numAdmins
invariant intended to guarantee that at least one admin always exists. The contracts override
revokeRole and renounceRole to keep that counter in sync, but they do not override
grantRole.
Because of that gap, DEFAULT_ADMIN_ROLE can be granted through the standard OpenZeppelin path
without incrementing _numAdmins.
Vulnerability details
In AviBridge.sol, the manual counter is only updated in the wrapper functions:
/// @notice Add a new admin address to the list of admins./// @param _admin New admin address.function addAdmin(address _admin) external onlyRole(DEFAULT_ADMIN_ROLE) { require(!hasRole(DEFAULT_ADMIN_ROLE, _admin), "Admin already added.");
_grantRole(DEFAULT_ADMIN_ROLE, _admin); _numAdmins++;}
/// @notice Remove an admin from the list of admins./// @param _admin Address to remove.function removeAdmin(address _admin) external onlyRole(DEFAULT_ADMIN_ROLE) { require(hasRole(DEFAULT_ADMIN_ROLE, _admin), "Address is not a recognized admin."); require (_numAdmins > 1, "Cannot remove the only admin.");
_revokeRole(DEFAULT_ADMIN_ROLE, _admin); _numAdmins--;}The issue is that the contracts disable revokeRole and renounceRole, but they do not override
grantRole, so the standard AccessControl path can still add an admin without touching
_numAdmins.
Impact
The admin counter can permanently desynchronize from the actual role set:
- a new admin is added with
grantRole(DEFAULT_ADMIN_ROLE, alice) _numAdminsstays unchanged- later removal paths rely on the stale counter rather than the real number of admins
Once the counter reaches 1 while multiple admins still exist, the role-management invariant is
broken and administrative cleanup can become stuck or behave incorrectly.
Reproduction
- Deploy
AviBridge.sol; the deployer starts withDEFAULT_ADMIN_ROLE. _numAdminsis initialized to1.- Call
grantRole(DEFAULT_ADMIN_ROLE, alice). alicenow has the admin role, but_numAdminsis still1.- Subsequent admin-removal flows now operate on stale accounting.
Recommendation
Override grantRole for the admin role and update _numAdmins whenever a new admin is added.
Alternatively, remove the manual counter and derive the invariant from role membership directly.