xanoutas/solidity-security-findings huggingface dataset:
Users can trick the paymaster to stole gas
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

The paymaster in the postOp is sending the different between the max cost and the actual gas used in the transaction:

```
file:src/MagicSpend/MagicSpend.sol
 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);  <------
        }
    }

```

The max cost is calculated base on the transaction that the user is gonna make, So malicious user can create a contract in which he can change the gas cost of the operation is he change some variables (see the POC). So the different between the maxGasCost and actualGasCost can be so big and the user is getting refunded this different.

## Impact
The magic spend can be drained by malicius users.

## Proof of Concept

See this example:
 
```
pragma solidity 0.8.19;

contract SimpleContract {

uint256 public loop = 100;

function changeLoop( uint256 x) public{
        loop = x;
   }

function trickPaymaster() public {
     for(uint256 i; i < loop; i++){
        // whatever logic
        
     }
}


}
```

The simple contract above can be used for a malicious user, he sent the transaction in his smart wallet, the off chain logic calculate the max cost of the transaction, at this point malicious user can change the variable loop to 0 sending a transaction with high gas to be include fast.

If everything was good malicious user get paid because the max cost will be far higher than the actualGasCost  of the transaction.

## Tools Used
Manual

## Recommended Mitigation Steps

Instead of sending the funds to the account consider hold the funds in the paymaster.


## Assessed type

Other
Incorrect signature validation for different signer policies
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
A Wallet can have from one to three owners and have different signer policies. Incorrect verification of signers in `_validateSignature()` in Wallet can lead to DOS (Denial of Service) of wallets or loss of funds.


## Proof of Concept
if the first owner loses their key, the other owners lose access to the Wallet.
 this is the wrong architecture for ERC-4337. Since EntryPoint considers that the Aggregator is returned (this is considered a valid signature).

## Tools Used
manual 

## Recommended Mitigation Steps
Limit the result of `_validateSignature()` to only 0 and 1.
Check the signer policy and owners to avoid a situation where the wallet checks zero signatures.Instead of checking the KYC of the first owner, simply consider any owner's signature as valid only if its owner passed KYC.



## Assessed type

Invalid Validation
ETH can be drained with malicious sender
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Paymaster's signature can be replayed to drain their deposits.


## Proof of Concept
Scenario :

user A delegate calls to Upgrader and upgrade it's sender contract to an attack contract
attack contract does not check any nonce and everything else is same to SmartAccount(but they can also add some other details to amplify the attack, but let's just stick it this way)
user A uses sig X(the one that used before) to initiate the same tx over and over
user A earnes nearly nothing but paymaster will get their deposits drained

## Tools Used

hardhat test, verified with livingrock

## Recommended Mitigation Steps
Since `validateUserOp` function is not limited to view function in erc4337 spec, add simple boolean data for mapping if hash is used or not




## Assessed type

ETH-Transfer
Anyone can withdraw from the MagicSpend contract
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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#L193


# Vulnerability details

## Impact
The `withdraw` function is unprotected and anyone can compose a withdrawal request and drain the contract at any time


## Proof of Concept
Leveraging on the MagicSpend tests, new withdrawal request was made for an unknown address address(1)

```solidity
 function _getSignature2() internal view returns (bytes memory signature) {
        bytes memory message = abi.encode(address(magic), address(1), block.chainid, asset, amount, nonce, expiry);
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, _toPrefixedMessageHash(message));
        signature = abi.encodePacked(r, s, v);
    }

function _getRequest2() internal view returns (MagicSpend.WithdrawRequest memory request) {
        return MagicSpend.WithdrawRequest({
            asset: asset,
            amount: amount,
            nonce: nonce,
            expiry: expiry,
            signature: _getSignature2()
        });
    }

```

and then called here

```solidity
function test_transfersERC20SuccessfullyOverview(uint256 amount_) public {
        vm.assume(amount_ > 1000000);
        amount = amount_;
        token.mint(address(magic), amount);

        asset = address(token);
        assert(token.balanceOf(withdrawer) == 0);

        vm.prank(address(1));
        magic.withdraw(_getRequest2());
        assert(token.balanceOf(address(1)) == amount);
    }
```

The results are as follows
```solidity
Compiler run successful!

Running 1 test for test/MagicSpend/Withdraw.t.sol:WithdrawTest
[PASS] test_transfersERC20SuccessfullyOverview(uint256) (runs: 256, μ: 138946, ~: 138946)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 251.68ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
```

## Tools Used
Manual Review, Foundry

## Recommended Mitigation Steps
- add access control to make it only callable by entryPoint by the modifier `onlyEntryPoint`
- verify the withdrawal Request against `_withdrawableETH[msg.sender]`


## Assessed type

Access Control
Due to the Smart Accounts never calling `IncrementNonce()` in the Entry Point contract Signature can be replayed multiple times if the User doesn't specify `MagicSpend.sol` as their paymaster.
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

## Recommended Mitigation Steps


## Assessed type

Invalid Validation
Malicious user's can grief The PayMaster(`MagicSpend.sol`) with the use of Gas-Bombs
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
> Note to understand this report a good knowledge of the `eth-infinitism` Entry Point is needed and a knowledge of Gas Bombs too.


When the EntryPoint handles the user operations the below process is followed:
- Validate the User's operations.
- Execute the User's operations.
- Handle the postOps.

The issue found originates during the execution of the User's operations:
https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWallet.sol#L272C2-L279C6
```solidity
    function _call(address target, uint256 value, bytes memory data) internal {
        (bool success, bytes memory result) = target.call{value: value}(data);
        if (!success) {
            assembly ("memory-safe") {
                revert(add(result, 32), mload(result))
            }
        }
    }
```
on the call to the target, the function uses assembly to copy the return data to memory, the issue here is that by using Gas Bombs(Read More About this [here](https://medium.com/@0xdeadbeef0x/the-double-edged-sword-of-abi-decode-f81529e62bcc)), the function `call()` can be made to revert with an `OOG` error, even though the call to the target was successful, in such a scenario and the call fails the issue stems from the fact that if a paymaster is being used, `eth-infinitism` entry point assumes that the paymaster is at fault:
https://github.com/eth-infinitism/account-abstraction/blob/abff2aca61a8f0934e533d0d352978055fddbd96/contracts/core/EntryPoint.sol#L64C2-L77C14
```solidity
    function _executeUserOp(uint256 opIndex, UserOperation calldata userOp, UserOpInfo memory opInfo) private returns (uint256 collected) {
        uint256 preGas = gasleft();
        bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset);

        try this.innerHandleOp(userOp.callData, opInfo, context) returns (uint256 _actualGasCost) {
            collected = _actualGasCost;
        } catch {
            bytes32 innerRevertCode;
            assembly {
                returndatacopy(0, 0, 32)
                innerRevertCode := mload(0)
            }
            // handleOps was called with gas limit too low. abort entire bundle.
            if (innerRevertCode == INNER_OUT_OF_GAS) {
                //report paymaster, since if it is not deliberately caused by the bundler,
                // it must be a revert caused by paymaster.
                revert FailedOp(opIndex, "AA95 out of gas");
            }
..SNIP..
    }
```
In this scenario, the bundlers take steps against the paymaster due to the waste of gas paid, and the stake of the paymaster is slashed, and if it happens again and again the paymaster will be banned by the bundlers.
Users can simply make calls to a target contract that returns an arbitrary excessively long data(Gas Bom
Deleted Users can still access Smart Wallet
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L102
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L77
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L201
https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L201


# Vulnerability details

## Impact
The function `removeOwnerAtIndex` is used to remove the owners from the list of owners from a smart wallet in the `MultiOwnable` contract. The deletion of the owner is from the line https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L106

The issue with this is deleting from storage array or struct doesn't reset the element and the data stored there can still be accessed except rewritten.

The function `isOwnerAddress` used to check if an address is an owner still returns true for removed address, which implies the `_checkOwner` function passes and the modifier `onlyOwner` is bypassed for removed owner

## Proof of Concept
A simple POC is updating the `testRemovesOwnerAtIndex` test function in the `RemoveOwnerAtIndex.t.sol` file to

```solidity
function testRemovesOwnerAtIndex() public {
        uint8 index = _index();
        vm.prank(owner1Address);
        _removeOwner();
        console2.log(mock.isOwnerAddress(owner1Address));
    }
```

We see a result of 
```
PASS] testRemovesOwnerAtIndex() (gas: 29284)
Logs:
  true
```

## Tools Used|
Manual Review, Foundry

## Recommended Mitigation Steps

We can instead reset the array value when deleting an owner like
```solidity
_getMultiOwnableStorage().isOwner[owner] = false;
_getMultiOwnableStorage().ownerAtIndex[index] = abi.encode(address(0));
```

Using pop might not easy here due to the importance of the index but can also be implemented


## Assessed type

Other
Incorrect implementation of `canSkipChainIdValidation`
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Function `canSkipChainIdValidation` allows to skip the chain ID validation for some predefined selectors. However, the list of selectors which can skip the chain ID validation is too huge. E.g., it's possible to perform replay attacks on other chains (because of the skipped chain ID validation) for adding or removing owners. 

## Proof of Concept

[File: CoinbaseSmartWallet.sol](https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWallet.sol#L180)
```
    function executeWithoutChainIdValidation(bytes calldata data) public payable virtual onlyEntryPoint {
        bytes4 selector = bytes4(data[0:4]);
        if (!canSkipChainIdValidation(selector)) {
            revert SelectorNotAllowed(selector);
        }

        _call(address(this), 0, data);
    }
```

Function `executeWithoutChainIdValidation()` executes `canSkipChainIdValidation()` to perform validation which checks if function can be executed without chain ID validation.

`canSkipChainIdValidation()` allows to skip chain ID validation for below selectors:

[File: CoinbaseSMartWallet.sol](https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/CoinbaseSmartWallet.sol#L252-L262)
```
    function canSkipChainIdValidation(bytes4 functionSelector) public pure returns (bool) {
        if (
            functionSelector == MultiOwnable.addOwnerPublicKey.selector
                || functionSelector == MultiOwnable.addOwnerAddress.selector
                || functionSelector == MultiOwnable.removeOwnerAtIndex.selector
                || functionSelector == UUPSUpgradeable.upgradeToAndCall.selector
        ) {
            return true;
        }
        return false;
    }
```

* `MultiOwnable.addOwnerPublicKey.selector`, `MultiOwnable.addOwnerAddress.selector` allows to replay the transaction on other chains.
Since the chaind ID validation is skipped - the attacker can replay the signature designed for adding new owner of the wallet address.

Let's consider a scenario:
1. Wallet is deployed on chain X
2. Users A, B, C were added as owners of the wallet
3. Users B and C decides to deploy similar wallet, but on chain Y. However, they do not want user A as the owner of that wallet.
4. Since chain ID validation is skipped for `MultiOwnable.addOwnerAddress.selector`, user A replays the signature from chain X on chain Y - adding himself as the owner of the wallet.

* `MultiOwnable.removeOwnerAtIndex.selector` allows to replay the signature on other chains
Since the chain ID validation is skipped - the attacker can replay the signature designed for removing owner at index X on another chain.

1. Two wallets are deployed on chain X and chain Y. Those wallets can have different owners.
2. At ch
addOwnerPublicKey is implemented incorrectly
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/main/src/SmartWallet/MultiOwnable.sol#L93-L95


# Vulnerability details

## Impact
The implementation of the `addOwnerPublicKey` function in MultiOwnable.sol is incorrect.

## Proof of Concept

The `addOwnerPublicKey` function takes two parameters *x* and *y* coordinates of the public key and concatenates them to get the address of the owner.
```
    /// @notice Convenience function to add a new owner passkey.
    ///
    /// @param x The owner public key x coordinate.
    /// @param y The owner public key y coordinate.
    function addOwnerPublicKey(bytes32 x, bytes32 y) public virtual onlyOwner {
        _addOwner(abi.encode(x, y));
    }
```
This adds an incorrect owner to the wallet, as the concatenation of public key coordinates does not give the address.

The correct implementation is this:
1. Concatenate x & y
2. Hash the concatenated value
3. Take the last 20 bytes to get the address


## Recommended Mitigation Steps
```
    /// @notice Convenience function to add a new owner passkey.
    ///
    /// @param x The owner public key x coordinate.
    /// @param y The owner public key y coordinate.
    function addOwnerPublicKey(bytes32 x, bytes32 y) public virtual onlyOwner {
        bytes memory xy = abi.encode(x, y);
        bytes32 hash = keccak256(xy);
        address owner = address(uint160(uint256(hash)));
        _addOwner(abi.encode(owner));
    }
```

# Resource

https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html



## Assessed type

Other
Loss of ETher and DOS when calling createAccount() function can be caused by an attacker
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

## Impact
- Loss of Ether and Denial of service can be caused by an attacker.

## Root cause
1. This is because an attacker can frontrun `create2` deterministic deployment to cause DOS.
2. Because `createAccount(...)` is payable and does not revert when deterministic deployment fails, the ETh sent with `createAccount` can be lost and stuck in the contract when `create2` is front-ran. 

Note that the revert in `LibClone.createDeterministicERC1967(...) ` is not bubbled up so the need to check the returned `alreadyCreated` and `revert` is necessary.

## Proof of Concept
The `LibClone.createDeterministicERC1967(...)` function used in the `createAccount(...)` function uses `create2(...)` to deterministically deploy a contract. The `create2(...)` deterministic deployment can be frontrun by anyone to cause denial of service.

The `createAccount(...)` function is `payable` and allows users to create and send ETher to the created account in the same transaction.

When a user calls the `createAccount(...)` function and an attacker frontruns that transaction to create a contract at the same address, the `alreadyCreated` returned variable is `true`. This is due to the fact that `create2` used in the `LibClone.createDeterministicERC1967` function creates a deterministic address which anyone can frontrun to take up the address.

The main issue is the the transaction that calls the `createAccount(...)` function does not revert when `alreadyCreated` returned variable is `true`. This will cause the ETher sent along with the transaction to be stuck in the CoinbaseSmartWallet contract forever.

```
File: CoinbaseSmartWalletFactory.sol
function createAccount(bytes[] calldata owners, uint256 nonce)
        public
        payable //@audit loss of value when already created.
        virtual
        returns (CoinbaseSmartWallet account)
    {
        if (owners.length == 0) {
            revert OwnerRequired();
        }

        (bool alreadyDeployed, address accountAddress) =//@audit revert not bubbled up so check alreadyDeployed and revert.
            LibClone.createDeterministicERC1967(msg.value, implementation, _getSalt(owners, nonce));

        account = CoinbaseSmartWallet(payable(accountAddress));

        if (alreadyDeployed == false) {//@audit ETh lost when alreadDeployed is true
            account.initialize(owners);
        }
    }
```
The `createDeterministicERC1967` function

```

function createDeterministicERC1967(uint256 value, address implementation, bytes32 salt)
        internal
        returns (bool alreadyDeployed, address instance)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3)
            mstore(0x40, 0x5155f3363d
Flawed Signature Validation in ERC-1271 Implementation
Audit: 2024-03-coinbase-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-coinbase/blob/e0573369b865d47fed778de00a7b6df65ab1744e/src/SmartWallet/ERC1271.sol#L69-L76


# Vulnerability details

## vulnerability details 
The isValidSignature function, as defined in the provided ERC-1271 abstract contract implementation, is responsible for validating signatures to authorize transactions or state modifications and the rolse of the functions  is expected to adhere strictly to the ERC-1271 standard, which mandates returning a specific magic value (0x1626ba7e) to signify a valid signature., it's contain an issue that is arise from the abstract nature of the _validateSignature method, which is left to be defined by implementing contracts. If improperly implemented, this could lead to a situation where invalid signatures are erroneously considered valid, thereby allowing unauthorized actions. 
```solidity
    function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
        if (_validateSignature({message: replaySafeHash(hash), signature: signature})) {
            // bytes4(keccak256("isValidSignature(bytes32,bytes)"))
            return 0x1626ba7e;
        }

        return 0xffffffff;
    }
```
## Impact
if an attacker exploit this can lead to unauthorized transfer of assets 
## Proof of Concept
let's say a  scenario where the _validateSignature function is incorrectly validates a malformed signature as valid:

- the Attacker crafts a signature that exploits the flawed logic in _validateSignature.
-  The attacker calls isValidSignature with their crafted signature and a hash representing an unauthorized action.
-  Due to the incorrect implementation, the function returns 0x1626ba7e, indicating the signature is valid.
-  With the signature erroneously validated, the attacker can perform actions such as transferring assets or altering contract states without legitimate authority.

## Tools Used
manual review 
## Recommended Mitigation Steps
it's should re-implement the _validateSignature function Using cryptographic librarie and a signature cannot be used more than once and 


## Assessed type

Other