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

Missing callvalue handling in `executeUserOp()`

Summary

Executions managed through the executeUserOp() function lack a mechanism to specify the ETH value sent in the call.

Vulnerability Details

The executeUserOp() function is one of the supported methods to trigger executions in the Nexus smart account.

158: function executeUserOp(PackedUserOperation calldata userOp, bytes32) external payable virtual onlyEntryPoint {
159: // Extract inner call data from user operation, skipping the first 4 bytes.
160: bytes calldata innerCall = userOp.callData[4:];
161: bytes memory innerCallRet = "";
162:
163: // Check and execute the inner call if data exists.
164: if (innerCall.length > 0) {
165: // Decode target address and call data from inner call.
166: (address target, bytes memory data) = abi.decode(innerCall, (address, bytes));
167: bool success;
168: // Perform the call to the target contract with the decoded data.
169: (success, innerCallRet) = target.call(data);
170: // Ensure the call was successful.
171: require(success, InnerCallFailed());
172: }
173:
174: // Emit the Executed event with the user operation and inner call return data.
175: emit Executed(userOp, innerCallRet);
176: }

As we can see in the code snippet, the implementation decodes the target address and the calldata from the received data. However, it provides no mechanism to specify the ETH value attached to the call.

Impact

ETH value cannot be specified for calls executed through the executeUserOp() function.

Tools Used

None.

Recommendations

Add support for a third argument that indicates the value to use in the call.

if (innerCall.length > 0) {
// Decode target address and call data from inner call.
- (address target, bytes memory data) = abi.decode(innerCall, (address, bytes));
+ (address target, uint256 value, bytes memory data) = abi.decode(innerCall, (address, uint256, bytes));
bool success;
// Perform the call to the target contract with the decoded data.
- (success, innerCallRet) = target.call(data);
+ (success, innerCallRet) = target.call{value: value}(data);
// Ensure the call was successful.
require(success, InnerCallFailed());
}
Updates

Lead Judging Commences

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

finding-cannot-msg.value-not-forwarded

Appeal created

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

finding-cannot-msg.value-not-forwarded

Support

FAQs

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