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

`Nexus.executeUserOp` does not forward `msg.value`

Summary

When executeUserOp forwards user calldata, it does not include msg.value in function call despite having the payable modifier.

Vulnerability Details

In executeUserOp, we see how calldata is decoded and forwarded to target address:

function executeUserOp(PackedUserOperation calldata userOp, bytes32) external payable virtual onlyEntryPoint {
// Extract inner call data from user operation, skipping the first 4 bytes.
bytes calldata innerCall = userOp.callData[4:];
bytes memory innerCallRet = "";
// Check and execute the inner call if data exists.
if (innerCall.length > 0) {
// Decode target address and call data from inner call.
(address target, bytes memory data) = abi.decode(innerCall, (address, bytes));
bool success;
// Perform the call to the target contract with the decoded data.
(success, innerCallRet) = target.call(data);
// Ensure the call was successful.
require(success, InnerCallFailed());
}
// Emit the Executed event with the user operation and inner call return data.
emit Executed(userOp, innerCallRet);
}

When the data is decoded into address, and bytes, which are for contract to call, and the exact call payload, however, in the low level call, msg.value is not included, which means when users calls the function with some Ether, those Ether will not pass to the destination.

Impact

msg.value will not be passed on, and potentially makes funds to stuck in wallet forever.

Tools Used

Manual review

Recommendations

Change the function to:

function executeUserOp(PackedUserOperation calldata userOp, bytes32) external payable virtual onlyEntryPoint {
// Extract inner call data from user operation, skipping the first 4 bytes.
bytes calldata innerCall = userOp.callData[4:];
bytes memory innerCallRet = "";
// Check and execute the inner call if data exists.
if (innerCall.length > 0) {
// Decode target address and call data from inner call.
(address target, bytes memory data) = abi.decode(innerCall, (address, bytes));
bool success;
// Perform the call to the target contract with the decoded data.
(success, innerCallRet) = target.call{value: msg.value}(data);
// Ensure the call was successful.
require(success, InnerCallFailed());
}
// Emit the Executed event with the user operation and inner call return data.
emit Executed(userOp, innerCallRet);
}
Updates

Lead Judging Commences

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