Core Contracts

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

`TimelockController.sol` does not enforce the required `EMERGENCY_DELAY` as stated in the docs

Summary

The TimelockController.sol is designed to manage time-delayed execution of governance proposals with role-based access control.
However, the contract has an implementation flaw in the emergency action scheduling function,
which does not enforce the required EMERGENCY_DELAY as stated in the documentation.

Vulnerability Details

  1. Missing Delay Enforcement for Emergency Actions

The scheduleEmergencyAction() allows emergency actions to be scheduled without enforcing the EMERGENCY_DELAY of 1 day, as specified in EMERGENCY_DELAY and the contract documentation.

Emergency actions have 1-day delay

This bypasses the intended delay mechanism, allowing immediate execution of emergency actions.

The function should store the scheduled timestamp and enforce a minimum delay before allowing execution, similar to how scheduleBatch() correctly enforces delays.

scheduleEmergencyAction() immediately marks the emergency action as scheduled, allowing execution without a delay.

Impact

The absence of a delay for emergency actions could enable the EMERGENCY_ROLE to bypass the intended waiting period, executing actions immediately.
This could lead to governance actions being executed without adequate oversight or the intended security buffer.

Tools Used

  • Manual Review

Recommendations

  • Modify scheduleEmergencyAction() to enforce a delay of EMERGENCY_DELAY before allowing execution.

  • Update executeEmergencyAction() to check if the required delay has passed before executing the action.

here is a proper implementation that will help fixing this issue:

  1. Add the following struct to ITimelockController.sol:

/**
* @notice Struct containing emergency action execution details
* @param timestamp Time when operation can be executed
* @param executed Whether operation has been executed
*/
struct EmergencyAction {
uint64 timestamp;
bool executed;
}
  1. Modify the _emergencyActions mapping to point to the new added struct:

- mapping(bytes32 => bool) private _emergencyActions;
+ mapping(bytes32 => EmergencyAction ) private _emergencyActions;
  1. Implement scheduleEmergencyAction() as follow:

/**
* @notice Schedules an emergency action
* @dev Only callable by addresses with EMERGENCY_ROLE
* @param id Operation ID for the emergency action
*/
function scheduleEmergencyAction(bytes32 id) external onlyRole(EMERGENCY_ROLE) {
+ uint256 timestamp = block.timestamp + EMERGENCY_DELAY;
+ _emergencyActions[id] = EmergencyAction({
+ timestamp: timestamp.toUint64(),
+ executed: false
+ });
- _emergencyActions[id] = true;
emit EmergencyActionScheduled(id, block.timestamp);
}
  1. Implement executeEmergencyAction()as follow:

/**
* @notice Executes an emergency action
* @dev Only callable by addresses with EMERGENCY_ROLE
* @param targets Target addresses for emergency calls
* @param values ETH values for emergency calls
* @param calldatas Calldata for emergency calls
* @param predecessor ID of operation that must be executed before
* @param salt Random value for operation ID
*/
function executeEmergencyAction(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt
) external payable onlyRole(EMERGENCY_ROLE) nonReentrant {
+ // Check emergency action status
+ EmergencyAction storage action = _emergencyActions[id];
+ if (action.timestamp == 0) revert EmergencyActionNotFound(id);
+ if (action.executed) revert EmergencyActionAlreadyExecuted(id);
+ // Check timing conditions
+ if (block.timestamp < action.timestamp) revert EmergencyActionNotReady(id);
bytes32 id = hashOperationBatch(targets, values, calldatas, predecessor, salt);
if (!_emergencyActions[id]) revert EmergencyActionNotScheduled(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 4 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 4 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.