xanoutas/solidity-security-findings huggingface dataset:
Potential bypass of `onlySelf` in `ContractDeployer.forceDeployOnAddress()`, allowing to mimic any address for the `msg.sender`
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/ContractDeployer.sol#L214-L233
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/ContractDeployer.sol#L238-L256
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/ContractDeployer.sol#L322-L356


# Vulnerability details

## Impact

The `ContractDeployer.forceDeployOnAddress()` function has the `onlySelf` modifier. This makes sure (among other things) that `_sender` param can't be specified directly, but will always be the `msg.sender`:

1. `ContractDeployer.forceDeployOnAddresses()` is being called.
1. `msg.sender` is passed into `ContractDeployer.forceDeployOnAddress()` (line 254 ContractDeployer.sol) as the `_sender` param.

Thus, the `_sender` param for `ContractDeployer.forceDeployOnAddress()` can't be specified directly, which is important. Otherwise `ContractDeployer.forceDeployOnAddress()` would allow any given address as the `msg.sender` because:

1. Without `onlySelf` you can specify any `_sender` for `ContractDeployer.forceDeployOnAddress()`.
1. Then the `_sender` is passed on to `ContractDeployer._constructContract()` (line 223 ContractDeployer.sol).
1. Then the mimicCall would take the `_sender` param to mimic the `msg.sender`, allowing to mimic any address as the `msg.sender` including governance addresses or even bootloader address to do literally anything, anywhere on the whole zkSync L2 chain.

The issue is that there is a way to specify any `_sender` for `ContractDeployer.forceDeployOnAddress()`:

1. A call is done to `ContractDeployer.forceDeployOnAddresses()` with a `_deployments` param that includes a `ForceDeployment` where `deployment.newAddress` is equal to the address of the `ContractDeployer`, and where `deployment.input` is containing the callData to call `ContractDeployer.forceDeployOnAddress()` with any `_deployment` param and especially with `_sender` param being any value to mimic any address as the `msg.sender`.
1. Doing so will subsequently make the `ContractDeployer` to call itself via the mimicCall (line 344 ContractDeployer.sol)), so the `ContractDeployer` calls it's own method `ContractDeployer.forceDeployOnAddress()` and the `_sender` param can be specified to any address as the `msg.sender`, as described before.

This bypasses the `onlySelf` modifier of `ContractDeployer.forceDeployOnAddress()` and allowing to mimic any address as the `msg.sender`.

If this issue is being abused, many things may be maliciously achieved since mimicing any `msg.sender` allows to do all kinds of attack vectors, especially bypassing access modifiers etc since the `msg.sender` can be set to any address.

## Proof of Concept

The force deployer calls `ContractDeployer.forceDeployOnAddresses()` with a `_deployments` param that includes a `ForceDeploymen
L1WethBridge.withdraw() encodes the address of ``l1Bridge`` instead of ``_l1Receiver``. As a result, the receiver will not receive the withdrawn ETH. 
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/DefaultAccount.sol#L45-L56


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
L1WethBridge.withdraw() encodes the address of ``l1Bridge`` instead of ``_l1Receiver``. As a result, the receiver will not receive the withdrawn ETH. 


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

L1WethBridge.withdraw() encodes the address of ``l1Bridge`` instead of ``_l1Receiver`` at L78

[https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/zksync/contracts/bridge/L2WethBridge.sol#L78](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/zksync/contracts/bridge/L2WethBridge.sol#L78)

To see this, lets look at L2EthToken.withdrawWithMessage():

[https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/L2EthToken.sol#L85-L93](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/L2EthToken.sol#L85-L93)

where L1_MESSENGER_CONTRACT.sendToL1(message) is called with message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, _to, _amount, _sender, _additionalData);
Here `_to`` is the ``l1Bridge`` instead of ``l1Receiver`` while ``_additionalData`` encoes ``l1Receiver``.

Then, we look at L1Messenger.sendToL1(message):

[https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/L1Messenger.sol#L119-L162)https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/L1Messenger.sol#L119-L162(

Finally, let's look at Mailbox.finalizeWithdrawl():

[https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L192-L216](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L192-L216)

which calls `` (address _l1WithdrawReceiver, uint256 _amount) = _parseL2WithdrawalMessage(_message);`` to extract the _l1WithdrawReceiver. We notice that, the ``l1Bridge`` instead of the ``l1Receiver`` is considered as the receiver. This is a mistake.

[https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L406-L428](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L406-L428)

In summary, L1WethBridge.withdraw() encodes the address of ``l1Bridge`` instead of ``_l1Re
Bootloader.yul does not process ETH value from l1 mailbox transaction properly
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#L323
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L346
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L1632
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L2986


# Vulnerability details

## Impact

Bootloader.yul does not process ETH value from l1 mailbox transaction properly

## Proof of Concept

In the current implementation, if we take a look at how the value of ETH minted and forwarded in l1 when requesting transction on mailbox, we can see that

when [_requestL2Transaction](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L323)

```solidity
params.sender = _sender;
params.txId = txId;
params.l2Value = _l2Value;
params.contractAddressL2 = _contractAddressL2;

params.expirationTimestamp = expirationTimestamp;
params.l2GasLimit = _l2GasLimit;
params.l2GasPricePerPubdata = _l2GasPerPubdataByteLimit;

params.valueToMint = msg.value;
params.refundRecipient = refundRecipient;

canonicalTxHash = _writePriorityOp(params, _calldata, _factoryDeps);
```

as we can see the _l2Value is set to

```solidity
params.valueToMint = msg.value;
```

then in _writePriorityOp, calling [_serializeL2Transaction](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L346)

```solidity
maxFeePerGas: uint256(_priorityOpParams.l2GasPrice),
maxPriorityFeePerGas: uint256(0),
paymaster: uint256(0),
// Note, that the priority operation id is used as "nonce" for L1->L2 transactions
nonce: uint256(_priorityOpParams.txId),
value: _priorityOpParams.l2Value,
reserved: [_priorityOpParams.valueToMint, uint256(uint160(_priorityOpParams.refundRecipient)), 0, 0],
data: _calldata,
```

note the reserved array

```solidity
reserved: [_priorityOpParams.valueToMint, uint256(uint160(_priorityOpParams.refundRecipient)), 0, 0],
```

according to the comment from struct

> @param value The value to pass with the transaction

while 

>  /// @param valueToMint The amount of ether that should be minted on L2 as the result of this transaction.

but when transaction is processed in bootloader, 

we are calling [NEAR_CALL_executeL1Tx](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L1632)

```solidity
function ZKSYNC_NEAR_CALL_executeL1Tx(
                abi,
                txDataOffset
) -> success {
```

then we call the code below to [mint ETH](https://github
Operator can drain non-native bridges
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#L3733
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/bootloader/bootloader.yul#L986-L989
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Executor.sol#L281-L282
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Executor.sol#L47


# Vulnerability details

## Impact

Non-native bridges rely on soft-confirmation when withdrawing funds, this includes zkSync official partners such as LayerSwap and Rhino. The operator can drain all such bridges since they have the ability to successfully commit false L1->L2 transactions with arbitrary data. The operator can with zero cost mimic any contract, including default accounts, on L2 by providing false L1->L2 transaction data in the bootloader. They can therefore withdraw from any default account through non-native bridges. Ultimately governance will revert the chain when the issue is noticed or when the execution of the batches fail, all L2 accounts will be refunded but the bridges will have been fully or partially drained.

The following is written in the [documentation](https://github.com/code-423n4/2023-10-zksync/blob/main/docs/Smart%20contract%20Section/Handling%20L1%E2%86%92L2%20ops%20on%20zkSync.md):

"Since the operator can put any data into the bootloader memory and for L1→L2 transactions the bootloader has to blindly trust it and rely on L1 contracts to validate it, it may be a very powerful tool for a malicious operator. Note, that while the governance mechanism is generally trusted, we try to limit our trust for the operator as much as possible, since in the future anyone would be able to become an operator."

The issue identified here is an example of not limiting the operator as much as possible since it is a exploitation path that can be fixed without removing any functionality.

## Proof of Concept

The POC is not coded since it would require a E2E test environment which has not been provided. Instead I will reference every relevant section of code and explain each step of the attack.

The operator decides which L1 transactions to include and inputs the details of each transaction in the memory of the bootloader:

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

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

False L1 transactions will be successfully executed in the bootloader since the legitimacy of the L1 transaction is only checked on L1:

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac
Attacker can submit more than 2048 logs in a single transaction to DOS the priority queue
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/Constants.sol#L100-L102


# Vulnerability details

## Impact
The merkle tree is only capable of processing 2048 logs in a batch. So an attacker can submit more than 2048 logs in a single transaction to DOS the priority queue.

## Proof of Concept
    /// @notice Public functionality to send messages to L1.
    function sendToL1(bytes calldata _message) external override returns (bytes32 hash) {
        uint256 gasBeforeMessageHashing = gasleft();
        hash = EfficientCall.keccak(_message);
        uint256 gasSpentOnMessageHashing = gasBeforeMessageHashing - gasleft();

        /// Store message record
        chainedMessagesHash = keccak256(abi.encode(chainedMessagesHash, hash));

        /// Store log record
        L2ToL1Log memory l2ToL1Log = L2ToL1Log({
            l2ShardId: 0,
            isService: true,
            txNumberInBlock: SYSTEM_CONTEXT_CONTRACT.txNumberInBlock(),
            sender: address(this),
            key: bytes32(uint256(uint160(msg.sender))),
            value: hash
        });
        _processL2ToL1Log(l2ToL1Log);

        // Get cost of one byte pubdata in gas from context.
        uint256 meta = SystemContractHelper.getZkSyncMetaBytes();
        uint32 gasPerPubdataBytes = SystemContractHelper.getGasPerPubdataByteFromMeta(meta);

        uint256 pubdataLen;
        unchecked {
            // 4 bytes used to encode the length of the message (see `publishPubdataAndClearState`)
            // L2_TO_L1_LOG_SERIALIZE_SIZE bytes used to encode L2ToL1Log
            pubdataLen = 4 + _message.length + L2_TO_L1_LOG_SERIALIZE_SIZE;
        }

        // We need to charge cost of hashing, as it will be used in `publishPubdataAndClearState`:
        // - keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) and keccakGasCost(64) when reconstructing L2ToL1Log
        // - keccakGasCost(64) and gasSpentOnMessageHashing when reconstructing Messages
        // - at most 2 times keccakGasCost(64) (as merkle tree can contain ~2*N leaves)
        uint256 gasToPay = pubdataLen *
            gasPerPubdataBytes +
            keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) +
            4 *
            keccakGasCost(64) +
            gasSpentOnMessageHashing;
        SystemContractHelper.burnGas(Utils.safeCastToU32(gasToPay));

        emit L1MessageSent(msg.sender, hash, _message);
    }
Function sendToL1 can be called by anyone. Attacker can craft a transaction to call this function 2049 times and put the transaction into the priority queue.

The call to will sendToL1 will succeed anyway, however, our merkle tree for logs is fix sized and only capable of processing 2048 logs in one batch. As a result, the transaction cannot be processed and the priority queue will stuck.

    function publishPubdataAndClearState(
        bytes calldata _totalL2ToL1PubdataAndStateDiffs
    ) external onlyCallFromBootloader {
[H] Incorrect legacy transaction hash logic
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/BootloaderUtilities.sol#L44
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/DefaultAccount.sol#L175


# Vulnerability details

## Impact
The BootloaderUtilities contract generates transaction hashes for different types based on the transaction data, 
including the signature. The signature includes a v value that encodes a parity bit y for address recovery purposes.

The way that the parity bit is encoded into v depends on the type of transaction.

For legacy transaction hashes, v can be either 27 + y, or 35 + chainid * 2 + y when the chainid is included.

https://ethereum.github.io/yellowpaper/paper.pdf

## Proof of Concept
However, the DefaultAccount contract enforces that v must be either 27 or 28. This causes problems in the legacy 
transaction hash function because, when the chainid is included in the encoding, the value of 35 + block.chainid * 2 is added to v. 

But, since v is already set to either 27 or 28, as enforced by the DefaultAccount contract, 
the resulting v value does not comply with the standard described above, thereby giving a non-standard transaction hash. 

Although there is an attempt to address this with the if block below when the chainid is included, it is never executed.

```
   // If the `chainId` is specified in the transaction, then the `v` value is encoded as
            // `35 + y + 2 * chainId == vInt + 8 + 2 * chainId`, where y - parity bit (see EIP-155).
            if (_transaction.reserved[0] != 0) {
                vInt += 8 + block.chainid * 2;
            }
```

Because it comes after the require statement that enforces v to be either 27 or 28. The existence of a chainId 
will change the encoded v value of the signature.

This means that the if block is never executed, the resulting transaction hash is always non-standard.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Its important to check for the existence of a chainId before checking the v value to be either 27 or 28.



## Assessed type

Invalid Validation
Everyone can call forceDeploy to deploy the system contract
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#L13


# Vulnerability details

## Impact
This bug will let anyone have the power to rewrite the system contract. And attacker can rewrite the logic of l2 bridge system contract. Then fake a withdraw log to l1 and steal user's fund.

## Proof of Concept
forceDeployOnAddresses[1] in ContractDeployer.sol limit only the msg.sender is FORCE_DEPLOYER or COMPLEX_UPGRADER_CONTRACT contract. And we can see from the notice[3] that contract ForceDeployUpgrader in ForceDeployUpgrader.sol is supposed to be used as an implementation of the ComplexUpgrader. However, we can see there's no modifer in forceDeploy[4] function. Which mean anyone can call forceDeploy. And then call the forceDeployOnAddresses with msg.sender == address(COMPLEX_UPGRADER_CONTRACT). Finally deploy bytecodes in any address which they want to rewrite. 

```solidity
function forceDeployOnAddresses(ForceDeployment[] calldata _deployments) external payable {----------------[1]
        require(
            msg.sender == FORCE_DEPLOYER || msg.sender == address(COMPLEX_UPGRADER_CONTRACT),
            "Can only be called by FORCE_DEPLOYER or COMPLEX_UPGRADER_CONTRACT"
        );  ----------------------[2]

        ....

    }

/// @notice It is supposed to be used as an implementation of the ComplexUpgrader.  ---------[3]
contract ForceDeployUpgrader {
    /// @notice A function that performs force deploy
    /// @param _forceDeployments The force deployments to perform.
    function forceDeploy(IContractDeployer.ForceDeployment[] calldata _forceDeployments) external {  ------------[4]
        IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses(_forceDeployments);  ----------------[5]
    }
}


```

## Tools Used
vscode, hardhat

## Recommended Mitigation Steps
Add a modifer in forceDeploy and limit only the admin can call this function.


## Assessed type

Access Control
Security consideration for upgrade transaction is not sufficient
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#L574


# Vulnerability details

## Impact
Security consideration for upgrade transaction is not sufficient. Malicious operator can still change bytecode of precompiles in the decribed way.

## Security consideration (cited from Handling L1→L2 ops on zkSync.md)
Since the operator can put any data into the bootloader memory and for L1→L2 transactions the bootloader has to blindly trust it and rely on L1 contracts to validate it, it may be a very powerful tool for a malicious operator. Note, that while the governance mechanism is generally trusted, we try to limit our trust for the operator as much as possible, since in the future anyone would be able to become an operator.

Some time ago, we used to have a system where the upgrades could be done via L1→L2 transactions, i.e. the implementation of the DiamondProxy upgrade would include a priority transaction (with from equal to for instance FORCE_DEPLOYER) with all the upgrade params.

In the Boojum though having such logic would be dangerous and would allow for the following attack:

Let’s say that we have at least 1 priority operations in the priority queue. This can be any operation, initiated by anyone.
The operator puts a malicious priority operation with an upgrade into the bootloader memory. This operation was never included in the priority operations queue / and it is not an upgrade transaction. However, as already mentioned above the bootloader has no idea what priority / upgrade transactions are correct and so this transaction will be processed.

The most important caveat of this malicious upgrade is that it may change implementation of the Keccak256 precompile to return any values that the operator needs.

When thepriorityOperationsRollingHash will be updated, instead of the “correct” rolling hash of the priority transactions, the one which would appear with the correct topmost priority operation is returned. The operator can’t amend the behaviour of numberOfPriorityTransactions, but it won’t help much, since the thepriorityOperationsRollingHash will match on L1 on the execution step.

That’s why the concept of the upgrade transaction is needed: this is the only transaction that can initiate transactions out of the kernel space and thus change bytecodes of system contracts. That’s why it must be the first one and that’s why emit its hash via a system L2→L1 log before actually processing it.

## Proof of Concept
In the paragraph above, it is mentioned that
>this is the only transaction that can initiate transactions out of the kernel space and thus change bytecodes of system contracts.

However, in current implementation, a normal L1->L2 transaction can also do that.

                switch getTxType(innerTxDataOffset) 
                    case 254 {
                        // This is an upgrade transaction.
                        // Pr