Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Valid

Bypass of EMERGENCY_DELAY in executeEmergencyAction due to Missing Delay Enforcement

Summary

The executeEmergencyAction function in the TimelockController contract allows privileged users with the EMERGENCY_ROLE to execute emergency actions immediately after scheduling them, without enforcing the intended EMERGENCY_DELAY (1 day). The lack of a delay check undermines the security assumptions of the timelock mechanism, potentially allowing malicious or erroneous emergency actions to be executed instantly.

Vulnerability Details

The TimelockController contract defines a constant delay:

uint256 public constant EMERGENCY_DELAY = 1 days;

However, in the executeEmergencyAction function, there is no verification to ensure that this delay has elapsed after scheduling .

  • The scheduleEmergencyAction function merely sets a boolean flag without recording the timestamp.

    function scheduleEmergencyAction(bytes32 id) external onlyRole(EMERGENCY_ROLE) {
    _emergencyActions[id] = true;
    emit EmergencyActionScheduled(id, block.timestamp);
    }

Since executeEmergencyAction only checks the boolean _emergencyActions[id] and deletes it without any time check, an actor with EMERGENCY_ROLE can schedule and immediately execute an emergency action in a single transaction.

Impact

Malicious actions such as fund transfers, protocol upgrades, or governance takeovers can be executed immediately by a compromised EMERGENCY_ROLE holder.

Tools Used

Manual Review

Recommendations

  1. ** Enforce EMERGENCY_DELAY:**
    Store the scheduling timestamp instead of a boolean in _emergencyActions.

    -- mapping(bytes32 => bool) private _emergencyActions;
    ++ mapping(bytes32 => uint256) private _emergencyActions;

2. Update scheduleEmergencyAction to record the timestamp:

function scheduleEmergencyAction(bytes32 id) external onlyRole(EMERGENCY_ROLE) {
-- _emergencyActions[id] = true;
++ _emergencyActions[id] = block.timestamp;
emit EmergencyActionScheduled(id, block.timestamp);
}

3. Add a delay check in executeEmergencyAction:

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);
++ if (_emergencyActions[id] == 0) revert EmergencyActionNotScheduled();
++ if (block.timestamp < _emergencyActions[id] + EMERGENCY_DELAY) {
++ revert EmergencyDelayNotMet(id);
++ }
delete _emergencyActions[id];
for (uint256 i = 0; i < targets.length; i++) {
(bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]);
if (!success) {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
}
revert CallReverted(id, i);
}
}
emit EmergencyActionExecuted(id);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

TimelockController emergency actions bypass timelock by not enforcing EMERGENCY_DELAY, allowing immediate execution

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

TimelockController emergency actions bypass timelock by not enforcing EMERGENCY_DELAY, allowing immediate execution

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!