HardhatFoundry
30,000 USDC
View results
Submission Details
Severity: low
Invalid

A malicious bootstrap can render any Nexus account re-initializable by an attacker, allowing them to set any malicious validator or executor on the account. This would enable the attacker to drain all funds from the account

https://github.com/Cyfrin/2024-07-biconomy/blob/9590f25cd63f7ad2c54feb618036984774f3879d/contracts/Nexus.sol#L218-L223

Summary

The Bootstrap contract can allow re-initialization of the account via a delegate call after the initialization , and allow any user to call initializeAccount() function which has no access control , which will lead to huge damage on the account , since it will allow any user to stael the funds of the account .

Vulnerability Details

the bootstrap contract is responsible for the initialization of the account as shown in the function initializeAccount()

function initializeAccount(bytes calldata initData) external payable virtual {
_initModuleManager();
(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes));
-> (bool success, ) = bootstrap.delegatecall(bootstrapCall);
require(success, NexusInitializationFailed());
}

this function is supposed to be called only once , since the function _initModuleManager() will revert if it called for the second time , so as shown in the function here _initModuleManager() , the function initializes the executors and the validators entries :

function _initModuleManager() internal virtual {
// account module storage
AccountStorage storage ams = _getAccountStorage();
-> ams.executors.init();
-> ams.validators.init();
}

the function init() in the SentinelListLib library will revert if it already initialized by checking the function alreadyInitialized()

function init(SentinelList storage self) internal {
-> if (alreadyInitialized(self)) revert LinkedList_AlreadyInitialized();
self.entries[SENTINEL] = SENTINEL;
}

the function alreadyInitialized checks that self.entries[SENTINEL] != ZERO_ADDRESS , if the self.entries[SENTINEL] equals to ZERO_ADDRESS this check will return false ,so it will allow re-initialization of the account ,because this check will not revert if (alreadyInitialized(self)) revert LinkedList_AlreadyInitialized(); ,
if the validators.entries[SENTINEL] is set to the ZERO_ADDRESS , and executors.entries[SENTINEL] is set to the ZERO_ADDRESS .

function alreadyInitialized(SentinelList storage self) internal view returns (bool) {
return self.entries[SENTINEL] != ZERO_ADDRESS;
}

In order to preform this attack , the bootstrap contract should access the storage slots that store self.entries[SENTINEL] in order to set them to the ZERO_ADDRESS , so the bootstrap contract can be like this :

contract bootstrap is BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgradeable {
function handle_initialization() public {
AccountStorage storage ams = _getAccountStorage();
-> ams.validators.entries[SENTINEL] = ZERO_ADDRESS ;
-> ams.executors.entries[SENTINEL] = ZERO_ADDRESS ;
}
function get_init_Data() public view returns (bytes memory) {
return abi.encode(address(this) , abi.encodeCall(this.handle_initialization , ()));
}
}

this will give the bootstrap contract the same storage as the Nexus account , so now when tthe Factory calls the initializeAccount on the Nexus account , as shown here
https://github.com/Cyfrin/2024-07-biconomy/blob/9590f25cd63f7ad2c54feb618036984774f3879d/contracts/factory/NexusAccountFactory.sol#L58-L61

if (!alreadyDeployed) {
INexus(account).initializeAccount(initData);
emit AccountCreated(account, eoaOwner, index);
}

this will call the bootstrap contract , then any malicious user can set the validators and the executors and steal all the funds of the account by validating invalid userOps .

Impact

allow re-initialization of the account which exposure the account to initialized by a malicious users and set malicious validators and executors and execute any userOp that drain all the funds fromt the account .

Tools Used

vscode

Recommendations

add this check after each delegateCall done by the account to prevent setting the validators.entries[SENTINEL] and executors.entries[SENTINEL] to the ZERO_ADDRESS .

function initializeAccount(bytes calldata initData) external payable virtual {
_initModuleManager();
(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes));
(bool success, ) = bootstrap.delegatecall(bootstrapCall);
+ // this check to make sure that the account is not initializable
+ AccountStorage storage ams = _getAccountStorage();
+ require (ams.executors.alreadyInitialized() && ams.validators.alreadyInitialized())
require(success, NexusInitializationFailed());
}
Updates

Lead Judging Commences

0xnevi Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Other
Assigned finding tags:

finding-front-running-initializeAccount

Invalid, - Checked [here](https://github.com/rhinestonewtf/sentinellist/blob/6dff696f39fb55bfdde9581544d788932f145e47/src/SentinelList.sol#L30-L32) based on `SentinelListLib` used as dependencies as seen [here](https://github.com/Cyfrin/2024-07-biconomy/blob/9590f25cd63f7ad2c54feb618036984774f3879d/contracts/interfaces/base/IStorage.sol#L34-L35). Contract cannot be reinitialized - front-running initializers invalid per [codehawks guidelines](https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity#findings-that-may-be-invalid)

Support

FAQs

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