Link to Affected Code:
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/TimelockController.sol#L237-L263
uint256 public constant EMERGENCY_DELAY = 1 days;
function executeEmergencyAction(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt
) external payable onlyRole(EMERGENCY_ROLE) nonReentrant {
bytes32 id = hashOperationBatch(targets, values, calldatas, predecessor, salt);
if (!_emergencyActions[id]) revert EmergencyActionNotScheduled(id);
delete _emergencyActions[id];
}
Description:
The TimelockController defines EMERGENCY_DELAY = 1 days
and documents emergency actions having a 1-day delay, but the executeEmergencyAction
function allows immediate execution after scheduling. The function lacks any validation against the EMERGENCY_DELAY
period between scheduling and execution. Emergency actions have 1-day delay
Impact:
The vulnerability allows:
Immediate execution of emergency actions
Bypass of intended safety delay
Breaks core safety mechanism of protocol governance
Proof of Concept:
uint256 scheduledTime = block.timestamp;
controller.scheduleEmergencyAction(id);
controller.executeEmergencyAction(
targets,
values,
calldatas,
predecessor,
salt
);
Recommended Mitigation:
Add delay enforcement to emergency actions:
function executeEmergencyAction(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt
) external payable onlyRole(EMERGENCY_ROLE) nonReentrant {
bytes32 id = hashOperationBatch(targets, values, calldatas, predecessor, salt);
if (!_emergencyActions[id]) revert EmergencyActionNotScheduled(id);
uint256 scheduledAt = emergencyActionSchedules[id];
if (block.timestamp < scheduledAt + EMERGENCY_DELAY)
revert EmergencyDelayNotElapsed();
delete _emergencyActions[id];
}