xanoutas/solidity-security-findings huggingface dataset:
`withdraw()` Non-ETH token is not supported by bundler + entry point
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# 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#L181
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L334


# Vulnerability details

## Impact
Non-ETH tokens are not supported in `withdraw()` when called through bundler + entry point. 

## Proof of Concept
Based on sponsor, `withdraw()` can be used in two ways. 
1. Direct usage by the SCW owner through the `execute()` function, enabling a call to `withdraw()` with a valid `withdrawRequest`.
2. Submitting an ERC-4337 UserOperation to the bundler, where `withdraw()` is executed within the transaction flow.

Current UI on Coinbase.com ONLY supports `withdraw()` through bundler + entry point per sponsor.

This report particularly focuses on scenario 2, where `withdraw()` is processed through the bundler + entry point.

It's important to highlight that the `withdraw()` function is designed to support both ETH and other valid tokens, as evident from the `_withdraw()` implementation.
```solidity
    function _withdraw(address asset, address to, uint256 amount) internal {
        if (asset == address(0)) {
            SafeTransferLib.safeTransferETH(to, amount);
        } else {
            SafeTransferLib.safeTransfer(asset, to, amount);
        }
    }
```

When utilizing the bundler + entry point, the process unfolds as follows:

1. The user submits an ERC-4337 UserOperation(`withdraw()`) to the bundler.
2. The bundler simulates validation of the UserOperation to ensure it can pay for its execution and includes it in a transaction.
3. The EntryPoint calls `validateUserOp()` with Smart Contract Wallet (SCW), then decreases PayMaster deposits.
4. EntryPoint triggers `validatePaymasterUserOp()` on MagicSpend/PayMaster. Within this function, it examines whether `withdrawRequest.asset != address(0)`. Given that address(0) denotes ETH, any non-ETH token will trigger a revert condition.

```solidity
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256 maxCost)
    external
    onlyEntryPoint
    returns (bytes memory context, uint256 validationData)
{
    WithdrawRequest memory withdrawRequest = abi.decode(userOp.paymasterAndData[20:], (WithdrawRequest));
    uint256 withdrawAmount = withdrawRequest.amount;

    if (withdrawAmount < maxCost) {
        revert RequestLessThanGasMaxCost(withdrawAmount, maxCost);
    }

  ->if (withdrawRequest.asset != address(0)) {
        revert UnsupportedPaymasterAsset(withdrawRequest.asset);
    }

    _validateRequest(userOp.sender, withdrawRequest);

    bool sigFailed = !isValidWithdrawSignature(userOp.sender, withdrawRequest);
    validationData = (sigFailed ? 1 : 0) | (uint256(withdrawRequest.expiry) << 160);

    if (address(this).balance < withdrawAmount) {
        revert InsufficientBalance(withdrawAmount, address(this).balance);
    }

    _withdrawableE
_withdraw  can  fail on zero amount transfers if amount is set to zero
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L334


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
withdraw amount can be zero, while  _withdraw do attempt to send it in such a case anyway as there is no check in place. Some ERC20 tokens do not allow zero value transfers, reverting such attempts.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.


 function _withdraw(address asset, address to, uint256 amount) internal {
        if (asset == address(0)) {
   @>         SafeTransferLib.safeTransferETH(to, amount);
        } else {
     @>       SafeTransferLib.safeTransfer(asset, to, amount);
        }
    }
## Tools Used

## Recommended Mitigation Steps

 function _withdraw(address asset, address to, uint256 amount) internal {
     if(amount>0)
{
        if (asset == address(0)) {
            SafeTransferLib.safeTransferETH(to, amount);
        } else {
            SafeTransferLib.safeTransfer(asset, to, amount);
        }
}
}


## Assessed type

Token-Transfer
Anyone with valid `WithdrawRequest` can steal excess ETH from `MagicSpend`
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/MagicSpend/MagicSpend.sol#L181-L194


# Vulnerability details

## Impact

Malicious users can create non-expiring `WithdrawRequest` and execute it when there are funds in the `MagicSpend` contract due to missing `_withdrawableETH` check in the `withdraw` function.

## Proof of Concept

Suppose that there is a scenario where `MagicSpend` has a non-zero balance - the owner calls `withdrawStake` or `withdrawTo` in `EntryPoint` passing the `MagicSpend` as a recipient, it is possible because of the exposed `receive()`. A malicious wallet owner can preventively issue a non-expiring signature from the contract owner and call `withdraw` when there are `amount` of tokens that do not belong to him, stealing them from other users/owner. 

We can see that the `_withdrawableETH` mapping is supposed to indicate the right amount of funds that belong to a sender, but the above-mentioned withdraw function lacks a check and allows a user with a signed `WithdrawalRequest` to front-run the owner of the funds.

[MagicSpend.sol#L181-L192](https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/MagicSpend/MagicSpend.sol#L181-L194)

```solidity
function withdraw(WithdrawRequest memory withdrawRequest) external {
      _validateRequest(msg.sender, withdrawRequest);

      if (!isValidWithdrawSignature(msg.sender, withdrawRequest)) {
          revert InvalidSignature();
      }

      if (block.timestamp > withdrawRequest.expiry) {
          revert Expired();
      }

      // reserve funds for gas, will credit user with difference in post op
      _withdraw(withdrawRequest.asset, msg.sender, withdrawRequest.amount);
  }
```

As we can see the only thing preventing attacker from executing the attack is the `isValidWithdrawSignature` , which will be bypassed by issuing owner’s signature in advance.

## Tools Used

Manual Review

## Recommended Mitigation Steps

In the `receive` function of the contract add the amount of received ETH to `_withdrawableETH[owner()]` . Then in the `withdraw` function use the mapping to indicate the proper withdrawable amount, also validating it against the `amount` argument from the signature.


## Assessed type

Access Control
Paymaster can be Banned Due to Unmitigated Front-running Attacks on Bundled Transactions
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L109


# Vulnerability details

## Impact

A malicious user could potentially exploit certain conditions to cause functions like `validatePaymasterUserOp` to revert consistently. If these reverts happen frequently, it could lead to the paymaster being flagged, deprioritized by the EntryPoint, or even banned due to perceived unreliability. 

## Proof of Concept
A bad actor could say observe a pending transaction in the bundler mempool that will significantly reduce the paymaster's balance, for instance a large withdrawal by a legitimate user. Before this transaction is mined, the frontrunner executes another transaction that further depletes the contract's balance. This action could make the original transaction fail due to insufficient funds.

[Github link](https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L133C9-L135C10)

```solidity
if (address(this).balance < withdrawAmount) {
            revert InsufficientBalance(withdrawAmount, address(this).balance);
        }
        
```


The EntryPoint relies on [validatePaymasterUserOp](https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L109C5-L140C6) to not revert under normal operation. Consistent reverts, especially due to balance issues caused by frontrunning, signal to the EntryPoint that the paymaster is unreliable.

    
### Scenario
Front-runners monitor the mempool for transactions indicating significant balance changes or withdrawals from the paymaster. They then submit their transactions with higher gas fees which leads to their transactions being mined before the observed transactions. 

This frontrunning can cause legitimate transactions to fail due to changed state conditions like insufficient balance, which in turn could cause `validatePaymasterUserOp` to revert consistently. Consequently signaling to Entrypoint that the paymaster is unreliable leading to it been banned or unprioritized for future transactions in the network.

## Tools Used
Manual Review

## Recommended Mitigation Steps
1. Use private transaction relayers or services like Flashbots to prevent frontrunners from observing and exploiting transactions in the public mempool.

2. Maintain a buffer of funds that are not immediately visible or accessible through withdrawal requests. This buffer can help ensure that observed transactions in the mempool do not lead to state changes that cause subsequent transactions to fail.




## Assessed type

DoS
Malicious State Alteration Impacting Bundler Rewards
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/27873bc8881bc79ea21d1868cb13b58ab58ffdbd/src/SmartWallet/CoinbaseSmartWallet.sol#L1-L336


# Vulnerability details

## Impact
A user can manipulates the state of their `CoinbaseSmartWallet` between a bundler's validation and transaction submission, causing the transaction to fail and the bundler to miss out on fees.

## Proof of Concept

Here's how such a scenario could unfold:

User Prepares Operation: Alice, a user of the `CoinbaseSmartWallet`, requests a `UserOperation` to be processed by a bundler. She provides all necessary information, including the current nonce and the set of wallet owners.

Bundler Validates: The bundler receives Alice's `UserOperation` and validates it. The bundler checks that the nonce is correct and that Alice's wallet has enough funds to cover the gas fees for the transaction.

Intentional State Change: After receiving confirmation of validation from the bundler but before the bundler submits the transaction, Alice intentionally calls the `executeBatch` function on her `CoinbaseSmartWallet`. She includes a transaction that updates the nonce and changes the ownership structure of the wallet. Alternatively, she could call `_authorizeUpgrade` to upgrade the contract to a new implementation, significantly altering the wallet's state.

Bundler Submits: The bundler, unaware of the recent changes, submits Alice's `UserOperation` to the network, expecting it to succeed as it had passed validation.

Transaction Fails: The network processes the bundler's submission, but the transaction fails because the state of Alice's wallet no longer matches the state expected by the UserOperation. The nonce is incorrect, the ownership may have changed, or the contract code is different due to the upgrade. As a result, the bundler does not receive the reward they anticipated for including the transaction.

[Transaction failure problem](https://notes.ethereum.org/@yoav/SkaX2lS9j).

## Tools Used
Manual review

## Recommended Mitigation Steps

Locking Mechanism: Implement a mechanism to lock the state during the critical period between a bundler's validation and the submission of the UserOperation.

Penalties: Implement penalties for users who frequently cause transactions to fail after validation.


## Assessed type

Error
Potential Vulnerability Due to Behavior of slice Function in the WebAuthn.sol
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/WebAuthnSol/WebAuthn.sol#L104-L162


# Vulnerability details


## Impact
The `WebAuthn.sol` contract which is a very critical component of the smart wallet system and is designed to verify WebAuthn Authentication Assertions. The contract's current implementation does not explicitly validate the length of the `clientDataJSON string` or the extracted slices, such as `_type`. This could potentially enable an attacker to exploit the contract by providing an empty or malformed clientDataJSON string that would still pass the validation checks.

To add more context, `slice` function in Solidity does not inherently validate the length of the extracted substring; it simply extracts a substring based on the provided indices. If the `clientDataJSON` string is shorter than expected, the slice function will return an empty string or a string shorter than expected, which could still pass the `keccak256` check if the expected hash value is also derived from an empty or shorter string 45.

The `keccak256` hash function is deterministic and will produce the same output for different inputs of the same length, even if the content is different. This means that if an attacker can manipulate the input string to be of the same length as the expected input, they could potentially bypass the validation checks by providing a malformed or empty `clientDataJSON` string that still results in a valid `keccak256` hash when compared against `EXPECTED_TYPE_HASH`

## Contract Analysis:

The `WebAuthn.sol` contract includes a `verify` function that processes a `WebAuthnAuth` struct, which contains a `clientDataJSON` string. This string is sliced to extract the type and challenge, but the contract lacks explicit checks to ensure that the `clientDataJSON` string is of the expected length or that the extracted slices are not empty.

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/WebAuthnSol/WebAuthn.sol#L116-L119
```solidity
string memory _type = webAuthnAuth.clientDataJSON.slice(webAuthnAuth.typeIndex, webAuthnAuth.typeIndex + 21);
if (keccak256(bytes(_type)) != EXPECTED_TYPE_HASH) {
    return false;
}
```

## Potential Exploit:

An attacker could provide an empty or malformed `clientDataJSON` string that would still pass the `keccak256` check against `EXPECTED_TYPE_HASH`. This could allow the attacker to bypass the verification process and potentially exploit the contract.

## POC Test Contract:

To demonstrate the potential issue, a test contract could be created that attempts to call the `verify` function with an empty or malformed `clientDataJSON` string. The test contract should be designed to pass the `keccak256` check but fail the length check, thus demonstrating the vulnerability.

```solidity
contract WebAuthnTest {
    WebAuthn webAuthn;

    constructor(WebAuthn _webAuthn) {
        webAuthn = _webAuthn;
`withdrawGasExcess()` does not check against `withdrawRequest.expiry`
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# 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 the `withdrawGasExcess()` 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:
1. Pay gas only
2. Transfer funds during execution only
3. Pay gas and transfer funds during execution

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.

1. The user submits an ERC-4337 UserOperation to the bundler.
2. The bundler simulates validation of the UserOperation to ensure it can pay for its execution and includes it in a transaction.
3. The EntryPoint calls `validateUserOp()` with Smart Contract Wallet (SCW), then decreases PayMaster deposits.
4. EntryPoint invokes `validatePaymasterUserOp()` on MagicSpend/PayMaster. In this function, `_validateRequest()` is triggered, flipping the corresponding `_nonceUsed` mapping to `true`. It also checks for `isValidWithdrawSignature()`.

```solidity
    function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256 maxCost)
        external
        onlyEntryPoint
        returns (bytes memory context, uint256 validationData)
    {
        WithdrawRequest memory withdrawRequest = abi.decode(userOp.paymasterAndData[20:], (WithdrawRequest));
        uint256 withdrawAmount = withdrawRequest.amount;

        if (withdrawAmount < maxCost) {
            revert RequestLessThanGasMaxCost(withdrawAmount, maxCost);
        }

        if (withdrawRequest.asset != address(0)) {
            revert UnsupportedPaymasterAsset(withdrawRequest.asset);
        }

        _validateRequest(userOp.sender, withdrawRequest);

        bool sigFailed = !isValidWithdrawSignature(userOp.sender, withdrawRequest);
        validationData = (sigFailed ? 1 : 0) | (uint256(withdrawRequest.expiry) << 160);

        if (address(this).balance < withdrawAmount) {
            revert InsufficientBalance(withdrawAmount, address(this).balance);
        }

        _withdrawableETH[userOp.sender] += withdrawAmount - maxCost;
        context = abi.encode(maxCost, userOp.sender);
    }
```

EntryPoint then executes `UserOperation.callData` on SCW, which invokes `withdrawGasExcess()` on MagicSpend.sol. No parameter is passed in here.
Two scenarios here:
1. User receives ETH during execution, and entry point proceed to call `postOp()`.
2. User receives ETH th
MagicSpend error length can cause DOS by reverting entire bundle transactions and reputation damage when transfer fails due to low balance
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/eth-infinitism/account-abstraction/blob/abff2aca61a8f0934e533d0d352978055fddbd96/contracts/core/EntryPoint.sol#L69
https://github.com/Vectorized/solady/blob/ec85d4a731c5f69aaa9a324d673f92dac0c29593/src/utils/SafeTransferLib.sol#L105-L112


# Vulnerability details

## Impact
The EntryPoint contract version (encoded in contract bytecode) reverts entire `EntryPoint.handleOps()` bundles if an `EntryPoint.innerHandleOp()` call reverts and returns an error of less than 32 bytes in length. This is due to EntryPoint version v0.6 attempting to `mload` a full word from the revert's return data, which renders it unable to process reverts less than the full word.

To take advantage of this, MEV bots watching the balance of MagicSpend can submit WithdrawRequests at a moment where they are able to target the right amount of balance depletion to trigger the only error returning such a revert length within `MagicSpend.validatePaymasterUserOp()`- the Solady `SafeTransferLib.ETHTransferFailed()` error (solely 4 byte error selector).

Despite directly draining bundler balance and MagicSpend paymaster reputation, this issue is marked "medium" since its balance will presumably generally be kept high enough to block all but the most formidable of whales. It is also presumably unlikely that the MagicSpend owner would sign a flash loan WithdrawRequest and such reverts should be easy to filter out at simulation.

## Proof of Concept
The following test file is designed for integration with the audit repository by being placed in a new "PoC" or other name dir within `2024-03-coinbase/test/SmartWallet/` and then run to observe the ability to deterministically grief attack by overloading insufficient prefund. This type of payload can be used by a malicious bundler or to target a bundler and expend their balance as well as reputation-ban the MagicSend paymaster.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Test, console2} from "forge-std/Test.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {UserOperation, UserOperationLib} from "account-abstraction/interfaces/UserOperation.sol";
import {IStakeManager} from "account-abstraction/interfaces/IStakeManager.sol";
import {calldataKeccak} from "account-abstraction/core/Helpers.sol";
import {MockEntryPoint} from "../mocks/MockEntryPoint.sol";
import "test/MagicSpend/PaymasterMagicSpendBase.sol";
import "../CoinbaseSmartWallet/SmartWalletTestBase.sol";

contract PoC is Test {
    /// forge-config: default.fuzz.runs = 10_000_000

    // dummy event for lazy selector encoding
    event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed);

    uint256 msOwnerPrivateKey = 0xdeadbeef;
    address msOwner = vm.addr(msOwnerPrivateKey);
    MagicSpend magicSpend = new MagicSpend(msOwner);
    CoinbaseSmartWallet accoun
CoinbaseSmartWallet initialize() can be frontrunned to set initial owners due to Access Control issue
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWallet.sol#L114-L120


# Vulnerability details

## Impact
The `initialize()` in the CoinbaseSmartWallet contract initializes the account with the the given owners, however there is no access control to ensure that the initialization of the contract is by authorized caller.

Therefore, any user can initialize the contract and set the initial owners for the contract. This also presents a case of possiblity of being frontrunned when a malicious user monitoring the mempool is able to identify the call and frontrun the call to initialize the contract.

## Proof of Concept
https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWallet.sol#L114-L120

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Test, console2, stdError} from "forge-std/Test.sol";
import {MockEntryPoint} from "../mocks/MockEntryPoint.sol";
import {MockCoinbaseSmartWallet} from "../mocks/MockCoinbaseSmartWallet.sol";
import "../../../src/SmartWallet/CoinbaseSmartWallet.sol";
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";
import {Static} from "./Static.sol";

contract InitTest is Test {
    CoinbaseSmartWallet public account;
    bytes[] owners;
    IEntryPoint entryPoint = IEntryPoint(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789);

    address anyUser = address(0xabc);
    bytes anyUserBytes = abi.encode(anyUser);

    function setUp() public virtual {
        vm.etch(0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789, Static.ENTRY_POINT_BYTES);
        account = new MockCoinbaseSmartWallet();
    }

    function testInitializeNoAccessControl() public {
        uint256 before = account.nextOwnerIndex();
        owners.push(anyUserBytes);
        vm.prank(anyUser);
        account.initialize(owners);
        assertEq(before + 1, account.nextOwnerIndex());
        
    }
}
```


## Tools Used
Manual Review

## Recommended Mitigation Steps
Apply access controls to ensure the function is called by an authorized caller. If the contract is an implementation contract, then apply the initializer modifier from [Openzeppelin's Initializable contract](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol)






## Assessed type

Access Control