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

Fallback Handlers Can Be Installed with Invalid Calltype

Summary

In ModuleManager contract, _installFallbackHandler(module, initData) installs fallback handler without checking the calltype value. This fallback handler cannot be called by Nexus.

Vulnerability Details

_installFallbackHandler(module, initData) installs fallback handler with following lines:

function _installFallbackHandler(address handler, bytes calldata params) internal virtual withRegistry(handler, MODULE_TYPE_FALLBACK) {
if (!IFallback(handler).isModuleType(MODULE_TYPE_FALLBACK)) revert MismatchModuleTypeId(MODULE_TYPE_FALLBACK);
// Extract the function selector from the provided parameters.
bytes4 selector = bytes4(params[0:4]);
// Extract the call type from the provided parameters.
CallType calltype = CallType.wrap(bytes1(params[4]));
// Extract the initialization data from the provided parameters.
bytes memory initData = params[5:];
// Revert if the selector is either `onInstall(bytes)` (0x6d61fe70) or `onUninstall(bytes)` (0x8a91b0e3).
// These selectors are explicitly forbidden to prevent security vulnerabilities.
// Allowing these selectors would enable unauthorized users to uninstall and reinstall critical modules.
// If a validator module is uninstalled and reinstalled without proper authorization, it can compromise
// the account's security and integrity. By restricting these selectors, we ensure that the fallback handler
// cannot be manipulated to disrupt the expected behavior and security of the account.
require(!(selector == bytes4(0x6d61fe70) || selector == bytes4(0x8a91b0e3)), FallbackSelectorForbidden());
// Revert if a fallback handler is already installed for the given selector.
// This check ensures that we do not overwrite an existing fallback handler, which could lead to unexpected behavior.
require(!_isFallbackHandlerInstalled(selector), FallbackAlreadyInstalledForSelector(selector));
// Store the fallback handler and its call type in the account storage.
// This maps the function selector to the specified fallback handler and call type.
_getAccountStorage().fallbacks[selector] = FallbackHandler(handler, calltype);
// Invoke the `onInstall` function of the fallback handler with the provided initialization data.
// This step allows the fallback handler to perform any necessary setup or initialization.
IFallback(handler).onInstall(initData);
}

"Calltype is bytes1;" is user-defined type. This calltype variable is decoded from params variable. But there are at most 2 type of calltype defined in the Nexus:

  • CALLTYPE_STATIC

  • CALLTYPE_SINGLE

So, it can configure calltype wrong.

Impact

ModuleManager's fallback function defined as:

fallback() external payable override(Receiver) receiverFallback {
FallbackHandler storage $fallbackHandler = _getAccountStorage().fallbacks[msg.sig];
address handler = $fallbackHandler.handler;
CallType calltype = $fallbackHandler.calltype;
require(handler != address(0), MissingFallbackHandler(msg.sig));
if (calltype == CALLTYPE_STATIC) {
assembly {
calldatacopy(0, 0, calldatasize())
// The msg.sender address is shifted to the left by 12 bytes to remove the padding
// Then the address without padding is stored right after the calldata
mstore(calldatasize(), shl(96, caller()))
if iszero(staticcall(gas(), handler, 0, add(calldatasize(), 20), 0, 0)) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
returndatacopy(0, 0, returndatasize())
return(0, returndatasize())
}
}
if (calltype == CALLTYPE_SINGLE) {
assembly {
calldatacopy(0, 0, calldatasize())
// The msg.sender address is shifted to the left by 12 bytes to remove the padding
// Then the address without padding is stored right after the calldata
mstore(calldatasize(), shl(96, caller()))
if iszero(call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0)) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
returndatacopy(0, 0, returndatasize())
return(0, returndatasize())
}
}
}

It's payable and open to wrong calltype vulnerability. It checks the calltype in here with 2 if statement and it will not catch any of those two if statement and transaction will and with no failure.

Scenario:

Bob installed a fallback handler to his Nexus with wrong calltype (example: 0x20)
Later, Alice wants to call that fallback handler with 1 ether for execution
Both if statements won't be catched because of invalid calltype and 1 ether will be on the Bob's Nexus without failure

Tools Used

Manual review

Recommendations

Following check will solve the problem

@@ -280,6 +281,7 @@ abstract contract ModuleManager is Storage, Receiver, EIP712, IModuleManagerEven
// Extract the call type from the provided parameters.
CallType calltype = CallType.wrap(bytes1(params[4]));
+ require(calltype == CALLTYPE_SINGLE || calltype == CALLTYPE_STATIC, InvalidCallType());
// Extract the initialization data from the provided parameters.
bytes memory initData = params[5:];
Updates

Lead Judging Commences

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

Appeal created

demorextess Submitter
11 months ago
demorextess Submitter
11 months ago
0xnevi Lead Judge
10 months ago
0xnevi Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-fallback-handler-non-supported

Support

FAQs

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