Privileged Role Migration Loophole
id: LS51H
title: Privileged Role Migration Loophole
baseSeverity: H
category: access-control
language: solidity
blockchain: [ethereum]
impact: Loss of contract control, fund theft, or governance takeover
status: draft
complexity: high
attack_vector: internal
mitigation_difficulty: hard
versions: [">=0.4.0", "<=0.8.25"]
cwe: CWE-862
swc: SWC-105
📝 Description
- Many smart contracts include privileged roles such as owner, admin, or governor. In upgradeable or modular systems, migration from one privileged role to another must be done carefully to avoid:
- Loopholes where roles are transferred but old roles are still active
- Race conditions where a new admin gains control before revoking the previous one
- Missing role cleanup, leaving both roles with concurrent privileges
- Insecure initialization, where role-setting logic is left unprotected
- This vulnerability typically emerges in custom role systems or upgradeable contracts (e.g., proxy + logic) when transferOwnership() or setAdmin() is called without clearing prior authorities or without locking down initialization.
🚨 Vulnerable Code
pragma solidity ^0.8.0;
contract RoleManager {
address public owner;
address public admin;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
function transferOwnership(address newOwner) external onlyOwner {
owner = newOwner;
}
function setAdmin(address newAdmin) external onlyOwner {
admin = newAdmin;
}
function emergencyWithdraw() external onlyAdmin {
// withdraw logic
}
}
🧪 Exploit Scenario
- Project owner calls transferOwnership(newOwner) and assumes all control is migrated.
- Old admin (malicious or disgruntled contributor) is still active.
- They call emergencyWithdraw() and drain funds or interrupt protocol logic.
- Alternatively, the new owner sets a new admin, but the old one was never removed or checked.
- Multiple privileged roles now exist concurrently without a clear single source of truth.
Assumptions:
- The role migration function is callable without delay or acceptance.
- The role grants permission to critical functions.
- No pendingGovernance / acceptGovernance() pattern is used
✅ Fixed Code
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureRoleManager is Ownable {
address public admin;
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
function setAdmin(address newAdmin) external onlyOwner {
require(newAdmin != address(0), "Zero address");
admin = newAdmin;
}
function revokeAdmin() external onlyOwner {
admin = address(0); // ✅ Explicit revocation of previous admin
}
function emergencyWithdraw() external onlyAdmin {
require(admin != address(0), "Admin revoked");
// withdraw logic
}
}
🧭 Contextual Severity
- context: "Mainnet protocol with owner-accessible role migration"
severity: H
reasoning: "High impact—roles can be reassigned or hijacked by insider or attacker."
- context: "Governance-controlled contracts with timelocks"
severity: M
reasoning: "Downgraded severity due to delay and visibility."
- context: "Contracts using accept-based role transfers"
severity: L
reasoning: "Loophole closed; safe migration flow."
🛡️ Prevention
Primary Defenses
- Use OpenZeppelin’s AccessControl or Ownable to manage roles formally.
- Always revoke old roles during migration (revokeRole, setX(address(0))).
Additional Safeguards
- Prevent use of critical functions if any privileged role is undefined or stale.
- Audit all role transitions in tests and simulate re-entrance paths.
Detection Methods
- Search for multiple privileged roles and their associated mutators.
- Check whether old role revocation is enforced during setRole() logic.
- Tools: Slither (reentrancy-eth, uninitialized-state), manual review of access control
🕰️ Historical Exploits
- Name: Lendf.Me DeFi Access Mismanagement
- Date: 2020-04
- Loss: ~$25M
- Post-mortem: Link to post-mortem
📚 Further Reading
✅ Vulnerability Report
id: LS51H
title: Privileged Role Migration Loophole
severity: H
score:
impact: 5
exploitability: 4
reachability: 4
complexity: 3
detectability: 3
finalScore: 4.15
📄 Justifications & Analysis
- Impact: Attackers or stale roles can hijack contract control or drain assets.
- Exploitability: Occurs if developers assume role transitions are atomic or exclusive.
- Reachability: Found in DAO, admin, or proxy systems with layered access.
- Complexity: Requires understanding multi-role lifecycle and invariants.
- Detectability: Requires human review or formal modeling of role transitions.