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
about 1 year ago
0xnevi Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Appeal created

demorextess Submitter
about 1 year ago
demorextess Submitter
about 1 year ago
0xnevi Lead Judge
about 1 year ago
0xnevi Lead Judge about 1 year 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.