xanoutas/solidity-security-findings huggingface dataset:
Withdrawer can drain `MagicSpend`
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The function `withdraw` in `MagicSpend.sol` is used to `allow caller to withdraw funds by calling with a valid withdrawRequest`

```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);
    }
```

The problem is that there can be unlimited amount of withdrawals as long as `_validateRequest` and `isValidWithdrawSignature` pass.

## Proof of Concept
As we can see in `validatePaymasterUserOp`, `postOp` and `withdrawGasExcess` the mapping `_withdrawableETH` is updated. However in `withdraw` that is not true and that allows the user to withdraw as much as he wants. This allows the user to withdraw as much as he wants.

1. Add a helper function to `MagicSpend.sol`.

```solidity
    function getWithdrawals(address account) external view returns (uint256) {
        return _withdrawableETH[account];
    }
```

2. Add test case to `MagicSpend.t.sol`.

```solidity
    function test_multipleETHWithdraws() public {
        vm.deal(address(magic), 100e18);

        console.log("Magic Spender initial balance: ", address(magic).balance);
        console.log("Withdrawer initial balance: ", withdrawer.balance);
        console.log("Withdrawer withdrawable ETH: ", magic.getWithdrawals(withdrawer));

        console.log("-------------------------------------------------------------------------------");
        console.log("Start withdrawing.");
        console.log("-------------------------------------------------------------------------------");
        
        for (uint i = 0; i < 10; i++) {
            bytes memory message = abi.encode(address(magic), withdrawer, block.chainid, asset, amount, i, expiry);
            (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, _toPrefixedMessageHash(message));
            bytes memory signature = abi.encodePacked(r, s, v);

            MagicSpend.WithdrawRequest memory request = MagicSpend.WithdrawRequest({
                asset: asset,
                amount: amount,
                nonce: i,
                expiry: expiry,
                signature: signature
            });

            vm.prank(withdrawer);
            magic.withdraw(request);
        }

        console.log("Magic Spender after withdrawals: ", address(magic).balance);
        console.log("Withdrawer after withdrawals: ", withdrawer.balance);
        console.log("Withdrawer withdrawable ETH: ", magic.getWithdrawal
Malicious user can take full control of an account during the creation
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWalletFactory.sol#L38-L56


# Vulnerability details

## Description
For every new wallet user, `CoinbaseSmartWalletFactory.createAccount()` gets called:
```javascript
    function createAccount(bytes[] calldata owners, uint256 nonce)
        public
        payable
        virtual
        returns (CoinbaseSmartWallet account)
    {
        if (owners.length == 0) {
            revert OwnerRequired();
        }

        (bool alreadyDeployed, address accountAddress) =
            LibClone.createDeterministicERC1967(msg.value, implementation, _getSalt(owners, nonce));

        account = CoinbaseSmartWallet(payable(accountAddress));

        if (alreadyDeployed == false) {
            account.initialize(owners);
        }
    }
```
In the parameters, the `owners` are specified, which will be the owners of the smart wallet, and the other parameter will be a nonce.
Both these parameters are used to create a deterministic ERC1967:
```javascript
-> LibClone.createDeterministicERC1967(msg.value, implementation, _getSalt(owners, nonce));
```

However, a malicious user can front-run any `createAccount()` transaction with a custom contract that uses a custom function that calls `createDeterministicERC1967`. In that custom function, the malicious user can call the `initialize()` function with a different address than the supplied `owners` parameter and get ownership of the account.
Note that in this scenario, `createAccount()` will not fail on the users' side because `alreadDeployed` will be `true`, therefore this `if-statement` will not be entered:
```javascript
	if (alreadyDeployed == false) {
            account.initialize(owners);
        }
```
The `createAccount()` function will succeed and it will return the `CoinbaseSmartWallet account`.

## Tools Used
Manual Review
## Recommended Mitigation Steps
Handle already deployed smartwallets differently.


## Assessed type

Context
Lack of Ownership Verification in Withdraw Function of MagicSpend Contract
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The impact of this finding is significant as it allows an attacker to withdraw assets from the contract that they do not own. By exploiting the lack of ownership verification in the withdraw function, an attacker can craft a valid WithdrawRequest with a valid signature for an asset they do not own and successfully withdraw that asset. This can lead to a loss of funds for the contract owner and other users.

## Proof of Concept
function withdraw(WithdrawRequest memory withdrawRequest) external {
    _validateRequest(msg.sender, withdrawRequest);

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

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

    // Vulnerable code: No ownership verification for the asset
    _withdraw(withdrawRequest.asset, msg.sender, withdrawRequest.amount);
}

To exploit this vulnerability, an attacker can craft a WithdrawRequest for an asset they do not own and call the withdraw function with this request. Since there is no ownership verification for the asset, the withdrawal will succeed, allowing the attacker to steal assets from the contract.

Proof of concept code and detailed steps for exploiting this vulnerability can be found in the GitHub repository at link to repository. Screenshots and logs demonstrating the successful withdrawal of assets by the attacker are also provided in the repository.

## Tools Used
No specialized tools were used to identify this vulnerability. Manual code review and analysis were sufficient to identify the lack of ownership verification in the withdraw function.

## Recommended Mitigation Steps
To mitigate this vulnerability, it is recommended to add ownership verification for the asset in the withdraw function. This can be done by maintaining a mapping of asset ownership in the contract and checking if the msg.sender is the owner of the asset before allowing the withdrawal. Additionally, it is advised to review other parts of the contract to ensure that similar vulnerabilities do not exist elsewhere.


## Assessed type

Access Control
validatePaymasterUserOp() call should not increase _withdrawableETH balance if signature is invalid  
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

When the MagicSpend contract receives a `validatePaymasterUserOp()` call from the entryPoint contract, it will increase the balance a user is entitled to through the 
` _withdrawableETH` mapping. The issue is that this balance will be increased even though the signature is not valid.

 

## Impact

Abuser will be able to add balance to their `_withdrawableETH` with invalid signatures enabling them to drain the MagicSpend account.

## Proof of Concept

As we can see in the [validatePaymasterUserOp](https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/MagicSpend/MagicSpend.sol#L138-L139) function,

```solidity
        _withdrawableETH[userOp.sender] += withdrawAmount - maxCost; 
        context = abi.encode(maxCost, userOp.sender);
```
`withdrawAmount` will be added to the balance of the sender. However if the signature is invalid this shouldn't be the case. This will allow an abuser to craft calls to increase their mapping balance with invalid signatures.

## Tools Used

Manual review

## Recommended Mitigation Steps

In order to mitigate this issue, if the signature happens to be invalid, the increase of the `_withdrawableETH` mapping should be ommitted

```
if(!sigFailed) {
       _withdrawableETH[userOp.sender] += withdrawAmount - maxCost; 
}
```


## Assessed type

Invalid Validation
`SELFBALANCE` opcode cannot be used during validation
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The contract balance cannot be queried in the validation code due to storage access restrictions of ERC-4337.

## Proof of Concept
The Paymaster uses the `SELFBALANCE` opcode during the validation phase:

```solidity
if (address(this).balance < withdrawAmount) {
    revert InsufficientBalance(withdrawAmount, address(this).balance);
}
```
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/MagicSpend/MagicSpend.sol#L133-L135

This is one of the Opcodes that it's blocked during validation:

```
	BALANCE (0x31)
	ORIGIN (0x32)
	GASPRICE (0x3A)
	BLOCKHASH (0x40)
	COINBASE (0x41)
	TIMESTAMP (0x42)
	NUMBER (0x43)
	PREVRANDAO/DIFFICULTY (0x44)
	GASLIMIT (0x45)
@>	SELFBALANCE (0x47)
	BASEFEE (0x48)
	GAS (0x5A)
	CREATE (0xF0)
	INVALID (0xFE)
	SELFDESTRUCT (0xFF)
```
https://docs.stackup.sh/docs/opcode-rules#op-011-blocked-opcodes

Additional info about this issue can be found in the TokenPaymaster template, even if it's a different model (it uses ERC20 as payment):

```solidity
	/// @title Sample ERC-20 Token Paymaster for ERC-4337
	/// This Paymaster covers gas fees in exchange for ERC20 tokens charged using allowance pre-issued by ERC-4337 accounts.
	/// The contract refunds excess tokens if the actual gas cost is lower than the initially provided amount.
@>	/// The token price cannot be queried in the validation code due to storage access restrictions of ERC-4337.
	/// The price is cached inside the contract and is updated in the 'postOp' stage if the change is >10%.
	/// It is theoretically possible the token has depreciated so much since the last 'postOp' the refund becomes negative.
	/// The contract reverts the inner user transaction in that case but keeps the charge.
	/// The contract also allows honest clients to prepay tokens at a higher price to avoid getting reverted.
	/// It also allows updating price configuration and withdrawing tokens by the contract owner.
	/// The contract uses an Oracle to fetch the latest token prices.
	/// @dev Inherits from BasePaymaster.
```

https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/TokenPaymaster.sol#L15-L25

## Tools Used
Manual review

## Recommended Mitigation Steps
It's difficult to provide a solution to this as it impacts the entire design, but blocked opcodes should be avoided during the validation phase.


## Assessed type

Context
'alreadyDeployed' should be change after  account  initialization.
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWalletFactory.sol#L53


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
if (alreadyDeployed == false) {
            account.initialize(owners);
        }

here we are not changing the alreadyDeployed after account.initialize(owners);
people can again call account.initialize(owners). 
## 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 createAccount(bytes[] calldata owners, uint256 nonce)
        public
        payable
        virtual
        returns (CoinbaseSmartWallet account)
    {
        if (owners.length == 0) {
            revert OwnerRequired();
        }

        (bool alreadyDeployed, address accountAddress) =
            LibClone.createDeterministicERC1967(msg.value, implementation, _getSalt(owners, nonce));

        account = CoinbaseSmartWallet(payable(accountAddress));

       
     @>> if (alreadyDeployed == false) {
            account.initialize(owners);
        }
    }
## Tools Used

## Recommended Mitigation Steps
if (alreadyDeployed == false) {
            account.initialize(owners);
        }
alreadyDeployed=true;



## Assessed type

Context
Non-compliance with EIP-4337 spec
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L137-L167


# Vulnerability details

## Impact
When validating signatures, the validateUserOp function must always return SIG_VALIDATION_FAILED if signature validation fails. 

According to EIP 4337 specifications:
'If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the userOpHash, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert.'

https://eips.ethereum.org/EIPS/eip-4337


## Proof of Concept
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/CoinbaseSmartWallet.sol#L137-L167

```solidity
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
        public
        payable
        virtual
        onlyEntryPoint
        payPrefund(missingAccountFunds)
        returns (uint256 validationData)
    {
        uint256 key = userOp.nonce >> 64;

        // 0xbf6ba1fc = bytes4(keccak256("executeWithoutChainIdValidation(bytes)"))
        if (userOp.callData.length >= 4 && bytes4(userOp.callData[0:4]) == 0xbf6ba1fc) {
            userOpHash = getUserOpHashWithoutChainId(userOp);
            if (key != REPLAYABLE_NONCE_KEY) {
                revert InvalidNonceKey(key);
            }
        } else {
            if (key == REPLAYABLE_NONCE_KEY) {
                revert InvalidNonceKey(key);
            }
        }

        // Return 0 if the recovered address matches the owner.
        if (_validateSignature(userOpHash, userOp.signature)) {
            return 0;
        }

        // Else return 1, which is equivalent to:
        // `(uint256(validAfter) << (160 + 48)) | (uint256(validUntil) << 160) | (success ? 0 : 1)`
        // where `validUntil` is 0 (indefinite) and `validAfter` is 0.
        return 1;
    }
```

## Tools Used

## Recommended Mitigation Steps

change 
```solidity
     revert InvalidNonceKey(key);
```

by

```solidity
return SIG_VALIDATION_FAILED;
```



## Assessed type

Error
Potential Front-Running grieving attack in CoinbaseSmartWalletFactory Contract
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWalletFactory.sol#L71-L84


# Vulnerability details



## Impact

Medium, as it results in a temporary DoS for users of the protocol.

The absence of `msg.sender` or some other unique parameter in the `_getSalt` function's computation could lead to a issue where two transactions with the same owners and nonce values result in the same deterministic address. Such an attack could result in temporary DoS for users of the protocol, as transactions intended for different addresses could be reverted due to conflicts in the deterministic address generation.

## Proof of Concept

The vulnerability is based on the deterministic address generation mechanism used in the `createAccount` function of the `CoinbaseSmartWalletFactory` contract. This mechanism relies on the `_getSalt` function to generate a unique salt for each account creation request. Without including `msg.sender` or `timestamp` or a `unique identifier` in the salt computation, transactions with the same `owners` and `nonce` values could result in the same salt, leading to the same deterministic address. This scenario could be exploited by a front-runner to observe a transaction and submit a similar transaction with a higher gas price, aiming to have it executed first.
Here is the relevant code snippet from `CoinbaseSmartWalletFactory.sol`:
https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWalletFactory.sol#L71-L84

```solidity
function _getSalt(bytes[] calldata owners, uint256 nonce) internal pure returns (bytes32 salt) {
    salt = keccak256(abi.encode(owners, nonce));
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

I would have suggested that the `_getSalt` function include `msg.sender` in its computation as shown below:

    ```solidity
    function _getSalt(bytes[] calldata owners, uint256 nonce) internal pure returns (bytes32 salt) {
        salt = keccak256(abi.encode(owners, nonce, msg.sender));
    }
    ```

But including `msg.sender` in the salt computation does not inherently resolve the issue because it does not differentiate between transactions initiated by different users. This is because `msg.sender` is the address of the account that is currently executing the contract, which is the same for all transactions initiated by the same user. Therefore, if two transactions from the same user are made with the same owners and nonce values, they would still result in the same salt, potentially leading to the same deterministic address being targeted by both transactions.

A more robust approach would be to introduce additional unique parameters into the salt computation, such as a `timestamp` or a `unique identifier` generated for each transaction. This will ensure that the deterministic address generation mechanism is truly unique for each transaction, re
MEV Exploitation in Account Abstraction: Risks to Smart Wallet Transactions
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
https://github.com/code-423n4/2024-03-coinbase/blob/27873bc8881bc79ea21d1868cb13b58ab58ffdbd/src\MagicSpend\MagicSpend.sol#L1-L341


# Vulnerability details

## Impact
The exploitation of MEV by Bundlers can have detrimental effects on the smart wallet ecosystem, leading to financial losses for users through manipulated transaction ordering, eroding trust as transactions may not be processed equitably, and causing network congestion due to increased transaction volumes from Bundlers seeking to maximize MEV profits.

## Proof of Concept

Bundler is an entity that submits the transaction to the Blockchain network on behalf of the users.
For the submission of the bundle transactions, the Bundler calls `handleOps()` of the [Entrypoint](https://basescan.org/address/0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789#code).
```javascript
// Any bundler can call the `handleOps` and execute the UserOp
// any one can become bundler no restriction
function handleOps(UserOperation[] calldata ops, address payable beneficiary) public nonReentrant {

        uint256 opslen = ops.length;
        UserOpInfo[] memory opInfos = new UserOpInfo[](opslen);

    unchecked {
        for (uint256 i = 0; i < opslen; i++) {
            UserOpInfo memory opInfo = opInfos[i];
            (uint256 validationData, uint256 pmValidationData) = _validatePrepayment(i, ops[i], opInfo);
            _validateAccountAndPaymasterValidationData(i, validationData, pmValidationData, address(0));
        }

        uint256 collected = 0;
        emit BeforeExecution();

        for (uint256 i = 0; i < opslen; i++) {
            collected += _executeUserOp(i, ops[i], opInfos[i]);
        }

        _compensate(beneficiary, collected);
    } //unchecked
    }
```

To illustrate the impact of MEV on smart wallets, consider a scenario where a user submits a UserOp to swap tokens at a favorable rate. A Bundler, spotting an opportunity for profit, could:

Front-run the user's swap by placing their own swap first, moving the market price against the user.

Back-run the user's swap by placing a trade immediately after, benefiting from the price movement caused by the user's initial swap.

Sandwich attack the user's swap by executing a buy order right before and a sell order right after the user's transaction, exploiting the price slippage.

Censor the user's transaction if it's not profitable for the Bundler to include, or if including it would prevent the Bundler's own transactions from being as profitable.

Manipulate gas prices by strategically ordering transactions to create artificial demand for block space, thus increasing gas prices.

the smart wallet submits the UserOp to the network, and the Bundler, acting as a miner, reorders the transactions within the block to maximize their own profits. The user's smart wallet, expecting a certain execu
no check for  input in 'postOp'
Audit: 2024-03-coinbase-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
There is no check on actualGasCost.It can be less than maxGasCost. it causes withdrawable to be reduced.
## 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 postOp(IPaymaster.PostOpMode mode, bytes calldata context, uint256 actualGasCost)
        external
        onlyEntryPoint
    {
        // `PostOpMode.postOpReverted` should be impossible.
        // Only possible cause would be if this contract does not own enough ETH to transfer
        // but this is checked at the validation step.
        assert(mode != PostOpMode.postOpReverted);

        (uint256 maxGasCost, address account) = abi.decode(context, (uint256, address));

        // Compute the total remaining funds available for the user accout.
        // NOTE: Take into account the user operation gas that was not consummed.
 @>       uint256 withdrawable = _withdrawableETH[account] + (maxGasCost - actualGasCost);

        // Send the all remaining funds to the user accout.
        delete _withdrawableETH[account];
        if (withdrawable > 0) {
            SafeTransferLib.forceSafeTransferETH(account, withdrawable, SafeTransferLib.GAS_STIPEND_NO_STORAGE_WRITES);
        }
    }
## Tools Used


## Recommended Mitigation Steps

if(maxGasCost - actualGasCost>0)
{
uint256 withdrawable = _withdrawableETH[account] + (maxGasCost - actualGasCost);
}


## Assessed type

Context