Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

[H-01] Unchecked Delegatecall in initializeRootUpgrade

Summary

The RootUpgrade library’s initializeRootUpgrade function performs a delegatecall to contracts listed in the initializables array without verifying their integrity. This design grants untrusted code execution privileges and completely compromises the calling contract’s logic.

Vulnerability Details

The initializeRootUpgrade function references an array of “initializables” and uses Address.functionDelegateCall on each contract within this array. The function does not validate or limit the trustworthiness of the addresses it calls.

function initializeRootUpgrade(address[] memory initializables) external {
// For each address in the array,
// the contract calls `delegatecall` without validating trustworthiness.
for (uint256 i = 0; i < initializables.length; i++) {
Address.functionDelegateCall(
initializables[i],
abi.encodeWithSignature("initialize()")
);
}
}

This design violates fundamental security guarantees by allowing unvetted contracts to run code with the deployer’s context. Attackers can insert a malicious contract into the initializables array, causing the protocol to execute arbitrary code during the upgrade process. This approach bypasses normal function access restrictions because delegatecall executes in the state context of the calling contract.

Impact

High

Attackers gain complete control over the caller’s state, enabling them to rewrite contract variables, re-route funds, or perform any operation that the contract owner can execute.

The vulnerability materializes whenever an untrusted or malicious contract address appears in the initializables array. If no strict governance or whitelisting process exists to lock down these addresses, the scenario inevitably arises under any compromised or careless upgrade procedure.

POC

An attacker deploys a contract named MaliciousInit with destructive logic in its fallback function.

// Attacker deploys this contract
contract MaliciousInit {
// Example of destructive logic: override some state variable
// in the calling contract's storage slot
fallback() external {
// This SSTORE operation modifies storage slot 0 (just as an example)
// The exact slot or logic depends on the calling contract’s layout
// and how the attacker wants to manipulate it
assembly {
sstore(0, 0xDEADBEEF)
}
}
}
  • The attacker or a compromised admin includes the attacker’s contract address in the initializables array.

  • Upon calling initializeRootUpgrade, the protocol executes delegatecall to MaliciousInit under the privileges of the RootUpgrade context, enabling unauthorized modifications to protocol state.

  • When MaliciousInit is delegate-called, its fallback() executes within the storage context of RootUpgrade. The malicious assembly code overwrites storage slot 0 with 0xDEADBEEF (or performs any other destructive action, such as transferring funds or setting an owner to a new address).

  • The attacker manipulates the state variables of the calling contract. They can reset ownership, drain tokens, or otherwise compromise the protocol’s logic.

Recommendations

Enforce strict validation on each address in the initializables array:

require(isTrustedContract(initializables[i]), "Untrusted contract");

Use a governance-approved list of verified contracts or a whitelisted registry. This measure ensures only vetted logic can execute delegatecalls, blocking malicious code from hijacking the upgrade process.

Updates

Lead Judging Commences

inallhonesty Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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