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

Missing call type validation for fallback handlers

Summary

A missing validation allows the installation of potentially unsupported types of fallback handlers.

Vulnerability Details

When installed, fallback handlers are configured with a specific call type. This is the same type of argument used in the execution mode of ERC-7579.

275: function _installFallbackHandler(address handler, bytes calldata params) internal virtual withRegistry(handler, MODULE_TYPE_FALLBACK) {
276: if (!IFallback(handler).isModuleType(MODULE_TYPE_FALLBACK)) revert MismatchModuleTypeId(MODULE_TYPE_FALLBACK);
277: // Extract the function selector from the provided parameters.
278: bytes4 selector = bytes4(params[0:4]);
279:
280: // Extract the call type from the provided parameters.
281: CallType calltype = CallType.wrap(bytes1(params[4]));

This parameter is then used to determine the type of call to execute the fallback handlers in fallback():

072: fallback() external payable override(Receiver) receiverFallback {
073: FallbackHandler storage $fallbackHandler = _getAccountStorage().fallbacks[msg.sig];
074: address handler = $fallbackHandler.handler;
075: CallType calltype = $fallbackHandler.calltype;
076: require(handler != address(0), MissingFallbackHandler(msg.sig));
077:
078: if (calltype == CALLTYPE_STATIC) {
079: assembly {
080: calldatacopy(0, 0, calldatasize())
081:
082: // The msg.sender address is shifted to the left by 12 bytes to remove the padding
083: // Then the address without padding is stored right after the calldata
084: mstore(calldatasize(), shl(96, caller()))
085:
086: if iszero(staticcall(gas(), handler, 0, add(calldatasize(), 20), 0, 0)) {
087: returndatacopy(0, 0, returndatasize())
088: revert(0, returndatasize())
089: }
090: returndatacopy(0, 0, returndatasize())
091: return(0, returndatasize())
092: }
093: }
094: if (calltype == CALLTYPE_SINGLE) {
095: assembly {
096: calldatacopy(0, 0, calldatasize())
097:
098: // The msg.sender address is shifted to the left by 12 bytes to remove the padding
099: // Then the address without padding is stored right after the calldata
100: mstore(calldatasize(), shl(96, caller()))
101:
102: if iszero(call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0)) {
103: returndatacopy(0, 0, returndatasize())
104: revert(0, returndatasize())
105: }
106: returndatacopy(0, 0, returndatasize())
107: return(0, returndatasize())
108: }
109: }
110: }

As we can see, the implementation only supports CALLTYPE_STATIC and CALLTYPE_SINGLE. However, the call type is not validated to be either of these values when the fallback handler is installed in _installFallbackHandler(), allowing the configuration of an unsupported type.

Additionally, the implementation of fallback() doesn't revert if calltype is neither CALLTYPE_STATIC nor CALLTYPE_SINGLE, leading to a "successful" execution from the caller's perspective, even though nothing is actually executed.

Impact

Unsupported fallback handlers can be installed in the Nexus smart account. When executed, these will silently succeed.

Tools Used

None.

Recommendations

When installing a fallback handler in _installFallbackHandler() ensure that calltype == CALLTYPE_STATIC || calltype == CALLTYPE_SINGLE.

// Extract the call type from the provided parameters.
CallType calltype = CallType.wrap(bytes1(params[4]));
+ require(calltype == CALLTYPE_STATIC || calltype == CALLTYPE_SINGLE);
Updates

Lead Judging Commences

0xnevi Lead Judge 10 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.