Closed c4-bot-10 closed 8 months ago
raymondfam marked the issue as insufficient quality report
raymondfam marked the issue as duplicate of #41
No runnable proof was given as in #41. Additionally, expiry is supposed to be imposed during validatePaymasterUserOp(), and not on withdrawGasExcess().
raymondfam marked the issue as duplicate of #167
3docSec marked the issue as not a duplicate
3docSec marked the issue as primary issue
The MagiSpend does not indeed check for signature expiry, but returns the info to the EntryPoint
that performs the check:
File: MagicSpend.sol
109: function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256 maxCost)
110: external
111: onlyEntryPoint
112: returns (bytes memory context, uint256 validationData)
---
128: validationData = (sigFailed ? 1 : 0) | (uint256(withdrawRequest.expiry) << 160);
129:
File: EntryPoint.sol
481: if (outOfTimeRange) {
482: revert FailedOp(opIndex, "AA32 paymaster expired or not due");
483: }
484: }
486: function _getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) {
487: if (validationData == 0) {
488: return (address(0), false);
489: }
490: ValidationData memory data = _parseValidationData(validationData);
491: // solhint-disable-next-line not-rely-on-time
492: outOfTimeRange = block.timestamp > data.validUntil || block.timestamp < data.validAfter;
493: aggregator = data.aggregator;
494: }
It's worth noting that this very verification is proven to happen as expected by the expectRevert
in the PoC of #167 .
3docSec marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L109 https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L169
Vulnerability details
Impact
The
WithdrawRequest.expiry
parameter is not being enforced in thewithdrawGasExcess()
function of the Magic Spend smart contract, leading to a violation of an invariant.Proof of Concept
When the withdrawing account is an ERC-4337 compliant smart contract (like Smart Wallet), there are three different ways the Magic Spend smart contract can be used:
Users can leverage assets from CoinBase.com as if they are on-chain, allowing them to cover gas costs and withdraw funds for various operations.
This report focuses specifically on the scenario "Transfer funds during execution only" and "Pay gas and transfer funds during execution", where user calls
withdrawGasExcess()
in execution.validateUserOp()
with Smart Contract Wallet (SCW), then decreases PayMaster deposits.validatePaymasterUserOp()
on MagicSpend/PayMaster. In this function,_validateRequest()
is triggered, flipping the corresponding_nonceUsed
mapping totrue
. It also checks forisValidWithdrawSignature()
.EntryPoint then executes
UserOperation.callData
on SCW, which invokeswithdrawGasExcess()
on MagicSpend.sol. No parameter is passed in here. Two scenarios here:postOp()
.postOp()
and rest of steps are triggered to complete the transaction.However, throughout this transaction, there is no enforcement of the
WithdrawRequest.expiry
. InvalidatePaymasterUserOp()
, only a valid nonce and signature are checked. Consequently,withdrawGasExcess()
transfers ETH without additional checks, violating the invariant:Tools Used
Manual Review
Recommended Mitigation Steps
Consider passing
WithdrawRequest
intowithdrawGasExcess()
as parameter.Assessed type
Context