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

Smart account permanently inoperable with multitype module uninstallation

Summary

When a multi-type module that includes MODULE_TYPE_VALIDATOR is uninstalled by their another type, this can bypass the check that there must still be one functional validator installed in the wallet. After uninstallation, the smart account could be permanently unusable.

Vulnerability Details

The Nexus Smart Accounts allows for installing modules of multiple types. However, the uninstallation process requires the module to be uninstalled by each type. If a multi-type module is uninstalled by a type other than MODULE_TYPE_VALIDATOR, the system may bypass the check that ensures at least one validator remains installed in the wallet.

This bypass can occur when a module acting as MODULE_TYPE_VALIDATOR and another type (e.g., HOOK, EXECUTOR, FALLBACK) is uninstalled. Consequently, the smart account could be left without a functional validator, making it permanently unusable.

Impact

If a multi-type module that includes MODULE_TYPE_VALIDATOR is uninstalled by a secondary type (e.g., HOOK, EXECUTOR, FALLBACK), the smart account can be left without a functional validator.

This conflict with the requirement that at least one functional validator must always be present. Consequently, the smart account becomes permanently unusable, failing to validate UserOperations and rendering the account inoperative.

However, this is likely to be an edge case, so the likelihood can be considered as LOW, but the impact is believed to be HIGH.

Proof of Concept

Initial State:

Step 1:

The user deploys the smart wallet and installs the DeadmanSwitch module using the RegistryBootstrap::initNexusScoped() function, which allows the user to initialize a Nexus account with the TYPE_VALIDATOR and TYPE_HOOK modules.

After installation, the user can use the smart wallet to support validation when EntryPoint calls and execute hooks as expected.

\\Location: RegistryBootstrap.sol
function initNexusScoped(
@> BootstrapConfig[] calldata validators,
@> BootstrapConfig calldata hook,
IERC7484 registry,
address[] calldata attesters,
uint8 threshold
) external {
// Initialize validators
for (uint256 i = 0; i < validators.length; i++) {
_installValidator(validators[i].module, validators[i].data);
}
// Initialize hook
if (hook.module != address(0)) {
_installHook(hook.module, hook.data);
}
_configureRegistry(registry, attesters, threshold);
}

Step 2:

The user uninstalls the module by calling Nexus::uninstallModule(moduleTypeId: MODULE_TYPE_HOOK, module: DeadmanSwitch, _). The uninstallation is successful, and the module triggers onUninstall().

This step bypasses the condition that the account retains at least one validator (ModuleManager::_uninstallValidator()) as theVALIDATOR becomes unusable.

\\Location: DeadmanSwitch.sol (https://github.com/rhinestonewtf/core-modules/blob/main/src/DeadmanSwitch/DeadmanSwitch.sol)
/**
* Handles the uninstallation of the module and clears the config
* @dev the data parameter is not used
*/
function onUninstall(bytes calldata) external override {
// delete the config
@> delete config[msg.sender];
// clear the trusted forwarder
clearTrustedForwarder();
emit ModuleUninitialized(msg.sender);
}

Outcome: The smart account becomes permanently inoperable as it fails to validate the UserOperation.

\\Location: DeadmanSwitch.sol (https://github.com/rhinestonewtf/core-modules/blob/main/src/DeadmanSwitch/DeadmanSwitch.sol)
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
)
external
override
returns (ValidationData)
{
// get the config for the sender
DeadmanSwitchStorage memory _config = config[userOp.sender];
// get the nominee
address nominee = _config.nominee;
// if nominee is not set, return validation failed
@> if (nominee == address(0)) return VALIDATION_FAILED; //@HERE the config[userOp.sender] has been deleted at the step 2
// check the signature of the nominee
bool sigValid = nominee.isValidSignatureNow({
hash: ECDSA.toEthSignedMessageHash(userOpHash),
signature: userOp.signature
});
uint48 validAfter = _config.lastAccess + _config.timeout;
config[userOp.sender].timeout = 0;
// return validation data
// if signature is invalid, validation fails
// if the timeout has not passed, validAfter will be when the timeout will pass
return _packValidationData({
sigFailed: !sigValid,
validAfter: validAfter,
validUntil: type(uint48).max
});
}

Tools Used

Manual Review

Recommendations

There is no code recommnedation but the optional approach provide below:

  • Require that the smart wallet must retain at least one ONE_TYPE_VALIDATOR module as default.

  • Ensure that the uninstallation logic properly handles multi-type modules and retains at least one functional validator.

Updates

Lead Judging Commences

0xnevi Lead Judge
11 months ago
0xnevi Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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