xanoutas/solidity-security-findings huggingface dataset:
Possible Exploitable Behavior: Requesting L2 Transactions Without Paying Gas Fees to the Operator
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L304-L306


# Vulnerability details

## Summary
When users request L2 transactions or send ETH to L2, they are required to include a minimum amount of gas to cover the operation [costs](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L1821-L1826), as specified in the bootloader. However, in requestL2Transaction function, there is a missing check for _l2GasLimit:

```solidity
            params.l2GasPrice = _isFree ? 0 : _deriveL2GasPrice(tx.gasprice, _l2GasPerPubdataByteLimit);
            uint256 baseCost = params.l2GasPrice * _l2GasLimit;
            require(msg.value >= baseCost + _l2Value, "mv"); // The `msg.value` doesn't cover the transaction cost
```

After [storing](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L356C14-L356C30) a transaction record in storage, it cannot be bypassed or deleted from PriorityOp. In cases of failure or insufficient gas to pay the bootloader, the value is sent to the refund recipient. Malicious users can take advantage of this situation and sent ETH to L2 without paying fees to the operator. Additionally, they can submit lengthy transactions to obtain bootloader memory or send multiple transactions since each batch has a limited number of transactions without requiring gas.
## Impact
- Malicious users can send ETH to L2 without paying fees to the operator.
- Malicious users can submit lengthy transactions to obtain bootloader memory or send multiple transactions, potentially overloading the system.
## Tools Used
Manual review 
## Recommendations
To prevent this possible exploit and maintain the integrity of the protocol, it is recommended to add a lower bound check for the gas limit in the requestL2Transaction function. This check ensures that the specified _l2GasLimit covers the transaction's cost. Here's an example of how this can be implemented:
```diff
+	uint256 gasBound; // Define the minimum gas limit.


function _requestL2Transaction(
.
.
.
        {
+           require(_l2GasLimit >= gasBound, "mv"); // The `_l2GasLimit` doesn't cover the transaction cost );
            params.l2GasPrice = _isFree ? 0 : _deriveL2GasPrice(tx.gasprice, _l2GasPerPubdataByteLimit);
            uint256 baseCost = params.l2GasPrice * _l2GasLimit;
            require(msg.value >= baseCost + _l2Value, "mv"); // The `msg.value` doesn't cover the transaction cost 
```


## Assessed type

Invalid Validation
Attacker can cause loss for other users of L2->L1 withdrawals by calling finalizeEthWithdrawal() for their withdrawals
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L186-L216
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L111-L121
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/L2EthToken.sol#L70-L93


# Vulnerability details

## Impact
to withdraw ETH from zkSync users and contracts need to call L2EthToken contract's `withdraw()` and `withdrawWithMessage()` functions and to receive the ETH in Ethereum user need to call MailBox's `finalizeEthWithdrawal()` function, and contract would send the ETH to `l1Receiver` address.

the issue is that when users are withdrawing the ETH, contract don't let them set any `MinGas` for their finalization transaction in the L1, and everybody can call `finalizeEthWithdrawal()` for all withdrawals. (assuming This function has public access which is the case, Allowlist is temporary solution)

So attacker can manipulate the gas of ETH withdrawal call to `l2Receiver` in Ethereum for other users withdrawals, we have this facts:
1. Most of projects and contracts in Ethereum are gas sensitive, it mean if gas was lower than some amount the execution path of that call can change. 
2. when MailBox sends ETH, it sends the remaining gas of transaction that is controllable by attacker.
3. when MailBox sends ETH to an address that is a contract, the `receive()` or `fallback()` function will be executed.
4. there may be some logics in `receive()` or `fallback()` functions that depend on gas (`if( gas() > ...) {}`).
5. the logics inside `receive()` or `fallback()` can be written to always not revert.
6. attacker can call `finalizeEthWithdrawal()` for others.

these shows that any user put his ETH at risk when sending his funds to a contract address in Ethereum because bridge doesn't let user to set a `MinGas` for his withdraw transaction in L1.

I believe this has high severity because it can apply to most of the transactions and the loss can be big too and attacker can perform it permissionless. all the bridges have `MinGas` feature that user will specify it in source network and bridge will give guaranty that the withdrawal transaction in the destination network will have `MinGas` at least. the root cause of the bug is: 
1. allowing others to call `finalizeWithdraw()` 
2. allowing them to set gas for final withdraw call.
3. not allowing user to specify `MinGas` for his withdrawal transction.

## Proof of Concept
This is `finalizeEthWithdrawal()`:
```javascript
    function finalizeEthWithdrawal(
        uint256 _l2BatchNumber,
        uint256 _l2MessageIndex,
        uint16 _l2TxNumberInBatch,
        bytes calldata _message,
        bytes32[] calldata _merkleProof
    ) external override nonReentrant senderCanCallFunction(s.allowList) {
        req
Efficient call incorrectly forwards `returnData` 
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/VirtualAccount.sol#L86-L112


# Vulnerability details

### Impact

​	Modified `returnData` is propagated

### Proof of Concept

EVM allows to return any amount of bytes.    Using `yul` you can do that by

    assembly{
    	return(memStart,memEnd)
    }



On the other hand, in Solidity,  ABI is used to encode `calldata` and data returned.

The `bytes` variable is a *dynamic array* as well as `int[]` 



Let's take a look at these contracts in Solidity that you can deploy in remix.



`First`  calls `Second` and `Second` calls `Third`  .

`Third` returns an array of bytes.    


    pragma solidity ^0.8.0;
    
    contract Third{
       
        
        ///0xa8fc655f
         function returnArrayofBytes(uint size) public pure returns (bytes memory){
                 bytes memory ret = new bytes(size);
                 for(uint i = 0;i<size;i++){
                          ret[i] = 0xab; 
                 }
                 return ret;  
                 }
              }
    
    
    contract Second{
        uint public returndatasize; /// Stores the returndatasize of Third
        
        function callThird(address _third,uint len) public returns (bytes memory returnData){
            uint size;
            assembly{
                mstore(0x0,0xa8fc655f) 
                mstore(0x20,len)
                let succes := call(gas(),_third,0,28,64,0,0x40)
                if iszero(succes){
                    revert (0x0,0x0)
                    }
                size := returndatasize()
                sstore(0,size) ///store the returned data size with ENCODING included 
            }
            // now I will introduce the bug as you do in EfficientCall contract
            returnData = new bytes(size);
            assembly{
                returndatacopy(add(returnData,0x20),0,size)
            }
        }
    
    }
    
    contract First{
            function callSecond(address _second, address _third,uint len) public returns (bytes memory returnData){
            return Second(_second).callThird(_third,len);
        }
    }
    

​	Note that `returndatasize()` is not the same in `Second` than in `Third` because  the encoding bytes unnecessary added.	



If you call `Third` with size 0 then you will receive 2 words (32 bytes each or 0x40), that is `returndatasize()` will be `0x40`. 

​	The first word is a pointer to the starting position of the array (0x20)

​	The second word is the length of the array, in this case is zero.



If you call `returnArrayofBytes(32)` then you will receive the following results.



`returndatasize()` will be `0x60` (96 bytes)

First word:          `0x20`

Second word:     `0x20 `

Third word :    `abababababababababababababababababababababababababababababababab`  



If you call,  `returnArrayofBytes(10) `  then the output will be **padded with zeros**

First word:          `0x20`

Second word:     `0x
Malicious L1 tx can DOS the state keeper by rewriting storage slots
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/72f5f16ed4ba94c7689fe38fcb0b7d27d2a3f135/code/system-contracts/contracts/Compressor.sol#L117-L195


# Vulnerability details

## Description
The conditional sealer https://github.com/matter-labs/zksync-era/blob/6cb8c2c350385ed96c0824869a885a7285735a91/core/lib/zksync_core/src/state_keeper/seal_criteria/conditional_sealer.rs  includes multiple checks regarding batch seal conditions. If certain values exceed the circuit's capacity, a proof cannot be generated. In such cases, the conditional sealer will flag the transaction as `SealResolution::Unexecutable` . Ultimately, the mempool will use the reject function to discard these transactions that exceed the circuit's capacity.

https://github.com/matter-labs/zksync-era/blob/6cb8c2c350385ed96c0824869a885a7285735a91/core/lib/zksync_core/src/state_keeper/io/mempool.rs#L226-L231


```rust
    async fn reject(&mut self, rejected: &Transaction, error: &str) {
        assert!(
            !rejected.is_l1(),
            "L1 transactions should not be rejected: {}",
            error
        );
```

As shown in the above code snippet, if an L1 transaction is flagged as Unexecutable, it will trigger a failed `assert!`, leading to state keeper crash. 

The conditional sealer does not have a specific branch for L1 transactions. The executability of L1 transactions relies on two factors: the maximum gas limit for L1 transactions and the elevated pubdata price (`REQUIRED_L2_GAS_PRICE_PER_PUBDATA`=800) .

There is the pubdata limits check in the conditional sealer. https://github.com/matter-labs/zksync-era/blob/6cb8c2c350385ed96c0824869a885a7285735a91/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/pubdata_bytes.rs#L25 

The pubdata size is obtained directly through a multiplication:


```RUST
    pub fn size(&self) -> usize {
        self.initial_storage_writes * InitialStorageWrite::SERIALIZED_SIZE
            + self.repeated_storage_writes * RepeatedStorageWrite::SERIALIZED_SIZE
    }
```

But in the new version Boojum, changes in the state diff compression method can allow attackers to use special values to rewrite storage slots, reducing the length of the compressed pubdata. In this way, malicious L1 transactions can potentially bypass the max gas limit and increase the `repeated_storage_writes` count to `SealResolution::Unexecutable`  branch.

## Impact
Malicious L1 TX can circumvent this limitation by exploiting the storage refund mechanism.

Attack Flow:

1. Create a new contract A on L2

2. Write the storage slots 0-1000 as bytes32(0xffff…ffff) in the batch#x

3. Write the storage slots 1000-2000 as bytes32(0xffff…ffff) in the batch#x+1

4. Wait for the next batch, and then send a L1 → L2 TX that reset the slots 0-2000 to `bytes32(0x00)`, and write the slots 2000-3000 as bytes32(0xffff…ffff) in the same TX.

Due to state diff compression, the storage change from 0xffff to 0x0000 in slots 0 to 2000 will be compressed as `Operatio
Malicious operator can force bootloader to call any address
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L1307-#L1332


# Vulnerability details

## Impact
Malicious operator can force bootloader to call function with selector EXECUTE_TX_SELECTOR of any address. If someone burn ETH to the bootloader address, the fund can also be stolen.

## Proof of Concept
            function ZKSYNC_NEAR_CALL_executeL2Tx(
                abi,
                txDataOffset
            ) -> success {
                // Skipping the first word of the ABI-encoding encoding
                let innerTxDataOffset := add(txDataOffset, 32)
                let from := getFrom(innerTxDataOffset)

                debugLog("Executing L2 tx", 0)
                // The tx.origin can only be an EOA
                switch isEOA(from)
                case true {
                    setTxOrigin(from)
                }  
                default {
                    setTxOrigin(BOOTLOADER_FORMAL_ADDR())
                }

                success := executeL2Tx(txDataOffset, from)
                debugLog("Executing L2 ret", success)
            }

            function executeL2Tx(txDataOffset, from) -> ret {
                ret := callAccountMethod({{EXECUTE_TX_SELECTOR}}, from, txDataOffset)
                
                if iszero(ret) {
                    debugReturndata()
                }
            }

In function ZKSYNC_NEAR_CALL_executeL2Tx, even if from is not an EOA, the bootloader will still call it with selector EXECUTE_TX_SELECTOR. This means malicious operator can force the bootloader call any address, even system contracts. If some system contract implements fallback in the future, it could be dangerous. Currently, the bootloader can call msgvalue simulator. If someone burns ether to the bootloader address, operator will be able to transfer them.

    fallback(bytes calldata _data) external onlySystemCall returns (bytes memory) {
        (uint256 value, bool isSystemCall, address to) = _getAbiParams();

        // Prevent mimic call to the MsgValueSimulator to prevent an unexpected change of callee.
        require(to != address(this), "MsgValueSimulator calls itself");

        if (value != 0) {
            (bool success, ) = address(ETH_TOKEN_SYSTEM_CONTRACT).call(
                abi.encodeCall(ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo, (msg.sender, to, value))
            );

            // If the transfer of ETH fails, we do the most Ethereum-like behaviour in such situation: revert(0,0)
            if (!success) {
                assembly {
                    revert(0, 0)
                }
            }
        }

        // For the next call this `msg.value` will be used.
        SystemContractHelper.setValueForNextFarCall(Utils.safeCastToU128(value));

        return EfficientCall.mimicCall(gasleft(), to, _data, msg.sender, false, isSystemCall);
    }

## Tools Used
Manual

## Recommended Mitigation Steps
R
Lack of access control for function `forceDeploy`
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/zksync/contracts/ForceDeployUpgrader.sol#L1-L16


# Vulnerability details

## Impact
Function forceDeploy in ForceDeployUpgrader has no access control, which means anyone can change bytecode of any contraact.

## Proof of Concept
    contract ForceDeployUpgrader {
        /// @notice A function that performs force deploy
        /// @param _forceDeployments The force deployments to perform.
        function forceDeploy(IContractDeployer.ForceDeployment[] calldata _forceDeployments) external {
            IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses(_forceDeployments);
        }
    }

Function forceDeploy in ForceDeployUpgrader has no access control, which means anyone can change bytecode of any contract.

    /// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses.
    /// @dev We do not require `onlySystemCall` here, since the method is accessible only
    /// by `FORCE_DEPLOYER`.
    function forceDeployOnAddresses(ForceDeployment[] calldata _deployments) external payable {
        require(
            msg.sender == FORCE_DEPLOYER || msg.sender == address(COMPLEX_UPGRADER_CONTRACT),
            "Can only be called by FORCE_DEPLOYER or COMPLEX_UPGRADER_CONTRACT"
        );

        uint256 deploymentsLength = _deployments.length;
        // We need to ensure that the `value` provided by the call is enough to provide `value`
        // for all of the deployments
        uint256 sumOfValues = 0;
        for (uint256 i = 0; i < deploymentsLength; ++i) {
            sumOfValues += _deployments[i].value;
        }
        require(msg.value == sumOfValues, "`value` provided is not equal to the combined `value`s of deployments");

        for (uint256 i = 0; i < deploymentsLength; ++i) {
            this.forceDeployOnAddress{value: _deployments[i].value}(_deployments[i], msg.sender);
        }
    }

This method is to be used only during an upgrade to set bytecodes on specific addresses. However, it is accessible anytime by anyone.

## Tools Used
Manual

## Recommended Mitigation Steps
Add access control


## Assessed type

CanAuto
Reentrancy Vulnerability in Address Library Function `functionCallWithValue`
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/main/code/system-contracts/contracts/openzeppelin/utils/Address.sol#L159


# Vulnerability details

## Impact
This vulnerability allows an attacker to perform reentrancy attacks on the smart contract. Specifically, the `functionCallWithValue` function from the Address library lacks proper security checks, enabling arbitrary external calls that can lead to unintended operations. This vulnerability can result in unauthorized fund transfers, unexpected state changes, and potential manipulation of contract logic.

The lack of proper checks in the `functionCallWithValue` function allows malicious contracts to recursively call back into the vulnerable contract before the state is updated, exploiting the reentrancy vulnerability. This can lead to unexpected reentrant calls, enabling attackers to repeatedly drain the contract's balance, perform unauthorized actions, or manipulate the contract's state.

## Proof of Concept
1. Attacker Deploys Malicious Contract
The attacker deploys a malicious contract that contains a fallback function, allowing the contract to receive Ether.

```solidity

contract MaliciousContract {
    fallback() external payable {
        // Perform reentrant call to the vulnerable contract's functionCallWithValue
        Address.functionCallWithValue(vulnerableContractAddress, data);
    }
}
```

2. Reentrant Call Exploitation
The attacker sends a transaction to the malicious contract, triggering the fallback function. Within the fallback function, a reentrant call is made to the vulnerable contract's functionCallWithValue.

3. Recursive Exploitation
Inside the `functionCallWithValue` function, the attacker's contract can repeatedly call back into the vulnerable contract before the state is updated, draining the contract's Ether balance and performing unauthorized actions.

## Tools Used
VS Code, Manual review

## Recommended Mitigation Steps
To mitigate this vulnerability, it is crucial to implement the reentrancy protection pattern, such as the Checks-Effects-Interactions pattern or reentrancy guard modifier.


## Assessed type

Reentrancy
Failure to check the gas limit causes the L2ERC20Bridge.finalizeDeposit() function to intentionally fail
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/zksync/contracts/bridge/L2ERC20Bridge.sol#L149


# Vulnerability details

## Impact
In the line https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/zksync/contracts/bridge/L2ERC20Bridge.sol#L149 there is a transfer of gas uint32(gasleft()) sufficient for making a third party call. However, this function call does not respect the 1/64 rule according to EIP-150 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md). An attacker could use this space to fail the function while the finalizeDeposit() function has not yet completed.

## Proof of Concept
EIP-150 introduces the "1/64 rule" whereby 1/64 of the available gas is reserved within the call context and the rest is forwarded to the external call. This means that the called function could potentially run out of gas, while the calling context could have some gas to eventually continue and complete execution successfully.

The attacker calls the L2ERC20Bridge.finalizeDeposit() function. At the same time, it transfers the amount of gas strictly calculated so that the call to the _deployBeaconProxy() function is not enough. When the finalizeDeposit() function is executed, which in turn calls the _deployL2Token() -> _deployBeaconProxy() functions. The parameter uint32(gasleft()) is passed to the _deployBeaconProxy() function when calling systemCallWithReturndata (https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/zksync/contracts/bridge/L2ERC20Bridge .sol#L149).

There is actually not enough gas to make an external call. This will prevent the external function call from throwing an error.

Influence:
The owner of the bridge token will lose access to the token because the job failed.

## Tools Used
Manual review

## Recommended Mitigation Steps
Transfer to external function 63/64 gas


## Assessed type

Context