xanoutas/solidity-security-findings huggingface dataset:
DoS at `Bridge::retryMessage` processing of failed messages when it invokes `_invokeMessageCall` helper function
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/taikoxyz/taiko-mono/blob/b47fc34cb0e7fe9b7ebd9416b3051a067483b860/packages/protocol/contracts/bridge/Bridge.sol#L331
https://github.com/taikoxyz/taiko-mono/blob/b47fc34cb0e7fe9b7ebd9416b3051a067483b860/packages/protocol/contracts/bridge/Bridge.sol#L477
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L497


# Vulnerability details

## Impact
The Bridge contract facilitates cross-chain communication and message passing between different chains. It processes retry failed messages `Bridge::retryMessage` among other functionality. It uses the helper function `Bridge::_invokeMessageCall` for invoking message calls, and utilises the `ExcessivelySafeCall` library to perform message calls with additional safety measures.

The `Bridge::_invokeMessageCall` function is able to forward calls to arbitrary addresses at the `_message.to` address. This potentially introduces a Denial of Service (DoS) vulnerability in the bridge retrying of failed messages, if the `_message.to` address is not properly validated or trusted. This DoS vulnerability can disrupt the cross-chain transactions, exhaust gas fees and undermine trust in the ecosystem.

Our tests noted 145x more gas (79,845,959) expended when invoking the `MaliciousContract2` contract in `Bridge.t.sol`, compared to the 549,882 when not invoking any malicious contract. It’s important to note that the gas limit specified in the message (gasLimit: 1000000) was exceeded, but the `retryMessage` function did not run out of gas. This may indicate either the defined `gaslimit` parameter or the `ExcessivelySafeCall` library is not operating as intended. 


## Proof of Concept

We added 1 malicious contract and 2 test functions into 'Bridge.t.sol' to assess the gas effects of `retryMessages` when interacting with a non-malicious and a malicious contract.

* Add this new contract into 'Bridge.t.sol':
```javascript
// A malicious contract that attempts to exhaust gas limits
contract MaliciousContract2 {
    fallback() external payable {
        while (true) {} 
    } 
}
```

* Add the following into the BridgeTest contract of 'Bridge.t.sol':
```javascript
    function test_retryMessage_InvokeMessageCall_DoS1() public {
        
        maliciousContract2 = new MaliciousContract2();

        vm.startPrank(Alice);

        (IBridge.Message memory message, bytes memory proof) =
            setUpPredefinedSuccessfulProcessMessageCall();

        // etch bad receiver at the to address, so it fails.
        vm.etch(message.to, address(badReceiver).code);

        bytes32 msgHash = destChainBridge.hashMessage(message);

        destChainBridge.processMessage(message, proof);
        IBridge.Status status = destChainBridge.messageStatus(msgHash);
        assertEq(status == IBridge.Status.RETRIABLE, true);
        vm.stopPrank();

        // Retry the message
        vm.prank(message.destOwner);
        dest
An address of 0 and an invalid signature could bypass the isValidSignature function.
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/libs/LibAddress.sol#L61


# Vulnerability details

## Impact

Byypass the isValidSignature function

If the signature is invalid, the ECDSA.recover(_hash, _sig) function will return the zero address.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5e3ba29b088f77483d1284b69d5c5927593ab457/contracts/utils/cryptography/ECDSA.sol#L144

The code `return ECDSA.recover(_hash, _sig) == _addr;` will check the `_addr` address. If the `_addr` address is the zero address, the code will return true even if the signature is invalid.


## Tools Used

vscode

## Recommended Mitigation Steps
Add a check to see if the _addr address is the zero address.





## Assessed type

Invalid Validation
adversary can bridge eth without send fee.
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L138


# Vulnerability details

## Impact
An user can bridge a eth without sending fee.
## Proof of Concept
look at this code :
```
  uint256 expectedAmount = _message.value + _message.fee;
        if (expectedAmount != msg.value) revert B_INVALID_VALUE();
```
The first impression looks like if someone bridges eth at no fee it will probably fail.
Unfortunately, there is no validation if ``_message.fee`` !=0. This allows users to fill the parameter with a zero value of ``_message.fee``.
Then users can bridge eth without fee.

So, actually variable ``_message.fee`` is useless.

**POC:**
Bridge.t.sol
```
  function test_Bridge_send_message_eth_without_fee() public {
        uint256 amount = 1 ether;
        IBridge.Message memory message = newMessage({
            owner: Alice,
            to: Alice,
            value: 1 ether,
            gasLimit: 1_000_000,
            fee: 0, // message.fee useless
            destChain: destChainId
        });
        vm.prank(Alice);
     (, IBridge.Message memory _message) = bridge.sendMessage{ value: amount}(message);
        assertEq(bridge.isMessageSent(_message), true);
}
```
## Tools Used
Manual
## Recommended Mitigation Steps
verify ``_message.fee`` != 0


## Assessed type

Invalid Validation
Block proposer can avoid paying assigned prover fees
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/0d081a40e0b9637eddf8e760fabbecc250f23599/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L80-L89
https://github.com/code-423n4/2024-03-taiko/blob/0d081a40e0b9637eddf8e760fabbecc250f23599/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L105-L117


# Vulnerability details

## Impact

As part of proposing new blocks, a proposer must pay the assigned prover a fee. this is done via what is called an assignment hook, implemented in AssignmentHooks.sol. 

All details regarding an assignment are passed by the proposer, and they are validated as follows:

            // Check assignment validity
        if (
            block.timestamp > assignment.expiry
                || assignment.metaHash != 0 && _blk.metaHash != assignment.metaHash
                || assignment.parentMetaHash != 0 && _meta.parentMetaHash != assignment.parentMetaHash
                || assignment.maxBlockId != 0 && _meta.id > assignment.maxBlockId
                || assignment.maxProposedIn != 0 && block.number > assignment.maxProposedIn
        ) {
            revert HOOK_ASSIGNMENT_EXPIRED();
        }

notice how the assignment.tierFees is not validated. A proposer can create tierfees struct with 0 fees as such:

        TaikoData.TierFee[] memory tierFees = new TaikoData.TierFee[](3);

        tierFees[0] = TaikoData.TierFee(LibTiers.TIER_OPTIMISTIC, 0 ether);
        tierFees[1] = TaikoData.TierFee(LibTiers.TIER_SGX, 0 ether);
        tierFees[2] = TaikoData.TierFee(LibTiers.TIER_GUARDIAN, 0 ether);


This will impact the code below :

         // Find the prover fee using the minimal tier
        uint256 proverFee = _getProverFee(assignment.tierFees, _meta.minTier);

        // The proposer irrevocably pays a fee to the assigned prover, either in
        // Ether or ERC20 tokens.
        if (assignment.feeToken == address(0)) {
            // Paying Ether
            _blk.assignedProver.sendEther(proverFee, MAX_GAS_PAYING_PROVER);
        } else {
            // Paying ERC20 tokens
            IERC20(assignment.feeToken).safeTransferFrom(
                _meta.coinbase, _blk.assignedProver, proverFee
            );
        }

the _getProver fee function will be passed the minimum tier required and the tierFees list created by proposer:

        function _getProverFee(
        TaikoData.TierFee[] memory _tierFees,
        uint16 _tierId
    )
        private
        pure
        returns (uint256)
    {
        for (uint256 i; i < _tierFees.length; ++i) {
            if (_tierFees[i].tier == _tierId) return _tierFees[i].fee;
        }
        revert HOOK_TIER_NOT_FOUND();
    }

But like mentioned earlier, the proposer can setup the Tierfee list in a way that will return a fee of 0. 


Due to this vulnerability, assigned prover's can lose out on all fees they should be earning.

## Tools Used
Manual Review


## Proof Of Concept

        function test_L1_ContestingWithSgxProof() external {
        gi
DoS at `Bridge::processMessage` processing of received messages when it invokes `_invokeMessageCall` helper function
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/taikoxyz/taiko-mono/blob/b47fc34cb0e7fe9b7ebd9416b3051a067483b860/packages/protocol/contracts/bridge/Bridge.sol#L282
https://github.com/taikoxyz/taiko-mono/blob/b47fc34cb0e7fe9b7ebd9416b3051a067483b860/packages/protocol/contracts/bridge/Bridge.sol#L477
https://github.com/taikoxyz/taiko-mono/blob/b47fc34cb0e7fe9b7ebd9416b3051a067483b860/packages/protocol/contracts/bridge/Bridge.sol#L497


# Vulnerability details

## Impact

The Bridge contract facilitates cross-chain communication and message passing between different chains. It processes received messages `Bridge::processMessage` and retry failed messages `Bridge::retryMessage`. among other functionality. It uses the helper function `Bridge::_invokeMessageCall` for invoking message calls, and utilises the `ExcessivelySafeCall` library to perform message calls with additional safety measures.

A Denial of Service (DoS) vulnerability can disrupt the cross-chain transactions, exhaust gas fees and undermine trust in the ecosystem. We noted the following:

* The `Bridge::_invokeMessageCall` function is able to forward calls to arbitrary addresses at the `_message.to` address. This potentially introduces a DoS vulnerability in the bridge processing of messages, if the _message.to address is not properly validated or trusted. 

* There is 5x more gas (1,302,134) expended when invoking the `MaliciousContract2` contract in `Bridge.t.sol`, compared to the 253,582 when invoking the `NonMaliciousContract1` contract. It’s important to note that the gas limit specified in the message (gasLimit: 1,000,000) was exceeded, but the processMessage function did not run out of gas. This may indicate either the defined `gaslimit` parameter or the `ExcessivelySafeCall` library is not operating as intended. Instead of reverting, the `processMessage` function continues to process which may not be the intent, but the `messageStatus` was noted as `IBridge.Status.RETRIABLE` (i.e. unsuccessful transaction).


## Proof of Concept

Perform 2 tests to assert:
* The processMessage function is successful (i.e. `IBridge.Status.DONE`) when invoking the `NonMaliciousContract1`.
* The processMessage function is unsuccessful (i.e. `IBridge.Status.RETRIABLE`) when invoking the `MaliciousContract2`.


For the 2 tests, add the following 2 new contracts into `Bridge.t.sol`:
```javascript
// A malicious contract that attempts to exhaust gas
contract MaliciousContract2 {

    fallback() external payable {
        while (true) {} // infinite loop
    }
    
}

// Non malicious contract that does not exhaust gas
contract NonMaliciousContract1 {
    fallback() external payable {
       
    }
}
```


Add the following into the `BridgeTest` contract of `Bridge.t.sol`:
```javascript

  NonMaliciousContract1. nonmaliciousContract1;
  MaliciousContract2  maliciousContract2;

  function test_processMessage_InvokeMessageCall_DoS1() public {
        nonmaliciousContract1 = new NonMaliciousContract1();
An attacker can Approve a proveblock.
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/TaikoL1.sol#L75
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/provers/GuardianProver.sol#L51


# Vulnerability details

## Impact
An attacker can allow malicious transactions with the proveBlock function.
## Proof of Concept
Look at the comment above ``GuardianProver.approve`` there is clear that function anly called by guardian and the verified by this execution:
```
        approved_ = approve(_meta.id, hash);
```
after execution, the approve function triggers ``TaikoL1.proveBlock`` with the previously verified parameters.
```
  bytes32 hash = keccak256(abi.encode(_meta, _tran));
        approved_ = approve(_meta.id, hash);

        if (approved_) {
            deleteApproval(hash);
         => ITaikoL1(resolve("taiko", false)).proveBlock(_meta.id, abi.encode(_meta, _tran, _proof));
        }
```
Unfortunately, since guardian verification only exists in the ``GuardianProver.approve`` function, an attacker can allow malicious transactions by calling ``TaikoL1.proveBlock`` without guardian verification. 

## Tools Used
Manual
## Recommended Mitigation Steps
ensure on ``TaikoL1.proveBlock`` has access control or verified parameter.


## Assessed type

Access Control
There could be not sending tokens if sender is not compliance with  RecallableSender interface
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L195-L206


# Vulnerability details

## Impact
onMessageRecalled will not be called and token will not be transfered to recipient if sender is  not compliance with  RecallableSender interface.

## Proof of Concept
           if (_message.from.supportsInterface(type(IRecallableSender).interfaceId)) {
                _storeContext(msgHash, address(this), _message.srcChainId);

                // Perform recall
                IRecallableSender(_message.from).onMessageRecalled{ value: _message.value }(
                    _message, msgHash
                );

                // Must reset the context after the message call
                _resetContext();
            } else {
                _message.srcOwner.sendEther(_message.value);
            }



When recall message happened , assuming invocation delay has already passed ,  there could be sending ether and tokens by on message recalled . However message.from is not compliance with RecallableSender interface , there will be only sending ether .No further erc 20 or 721 or 1155 tokens will not be transfered . Even tho sender already send tokens to vault or burned , recipient will get nothing . This may lead to loss of funds for users.
## Tools Used
manual view
## Recommended Mitigation Steps
also sending tokens along with ether


## Assessed type

DoS
recall message will be DOS cause of invalid validation 
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/bridge/Bridge.sol#L166


# Vulnerability details

## Impact
recall message will be DOS cause of the invalid validation .

## Proof of Concept
To be recall , message status has to be failed status , however protocol check that message status of recall has to be new , otherwise tx will be reverted .And there is also check that fail signal has to be received . To be received fail signal , message status has to be failed . 


    if (messageStatus[msgHash] != Status.NEW) revert B_STATUS_MISMATCH();


    if (!_proveSignalReceived(signalService, failureSignal, _message.destChainId, 
      _proof)) {
                revert B_NOT_FAILED();
            }


       function _updateMessageStatus(bytes32 _msgHash, Status _status) private {
        if (messageStatus[_msgHash] == _status) return;

        messageStatus[_msgHash] = _status;
        emit MessageStatusChanged(_msgHash, _status);

        if (_status == Status.FAILED) {
            ISignalService(resolve("signal_service", false)).sendSignal(
                signalForFailedMessage(_msgHash)
            );
        }
    }

## Tools Used
manual view
## Recommended Mitigation Steps
change to message status has to be failed


## Assessed type

DoS
Signature replay can be used to withdraw victim funds
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/team/TimelockTokenPool.sol#L168-L172


# Vulnerability details

## Impact
A signature replay attack is possible, which leads to the withdrawal of user funds.


## Proof of Concept

```solidity
function withdraw(address _to, bytes memory _sig) external {
        if (_to == address(0)) revert INVALID_PARAM();
        bytes32 hash = keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", _to));
        address recipient = ECDSA.recover(hash, _sig);
        _withdraw(recipient, _to);
    }
```
No nonce is used in the [hash](https://github.com/code-423n4/2024-03-taiko/blob/main/packages/protocol/contracts/team/TimelockTokenPool.sol#L170)

**Flow of the attack:** 
1. Grant is approved to A 
2. A allows B to withdraw by signing the hash
3. B withdraws on behalf of A
4. A gets a New grant approved
5. B uses the same previous sig, to withdraw all A funds again! 

## Recommended Mitigation Steps

A nonce should be used while constructing hash.


## Assessed type

Invalid Validation
Gas issuance is inflated and will halt the chain or lead to incorrect base fee
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L140-L143
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L262-L293
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L145-L152
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L140-L143


# Vulnerability details

## Impact

The base fee calculation in the `anchor()` function is incorrect. Issuance is over inflated and will either lead to the chain halting or a severely deflated base fee.
## Proof of Concept

We calculate the 1559 base fee and compare it to `block.basefee`
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L140-L143

```solidity
        (basefee, gasExcess) = _calc1559BaseFee(config, _l1BlockId, _parentGasUsed);
        if (!skipFeeCheck() && block.basefee != basefee) {
            revert L2_BASEFEE_MISMATCH();
        
```

But the calculation is incorrect

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L2/TaikoL2.sol#L262-L293

```solidity
        if (gasExcess > 0) {
            // We always add the gas used by parent block to the gas excess
            // value as this has already happened
            uint256 excess = uint256(gasExcess) + _parentGasUsed;

            // Calculate how much more gas to issue to offset gas excess.
            // after each L1 block time, config.gasTarget more gas is issued,
            // the gas excess will be reduced accordingly.
            // Note that when lastSyncedBlock is zero, we skip this step
            // because that means this is the first time calculating the basefee
            // and the difference between the L1 height would be extremely big,
            // reverting the initial gas excess value back to 0.
            uint256 numL1Blocks;
            if (lastSyncedBlock > 0 && _l1BlockId > lastSyncedBlock) {
                numL1Blocks = _l1BlockId - lastSyncedBlock;
            }

            if (numL1Blocks > 0) {
                uint256 issuance = numL1Blocks * _config.gasTargetPerL1Block;
                excess = excess > issuance ? excess - issuance : 1;
            }

            gasExcess_ = uint64(excess.min(type(uint64).max));

            // The base fee per gas used by this block is the spot price at the
            // bonding curve, regardless the actual amount of gas used by this
            // block, however, this block's gas used will affect the next
            // block's base fee.
            basefee_ = Lib1559Math.basefee(
                gasExcess_, uint256(_config.basefeeAdjustmentQuotient) * _config.gasTargetPer
Prover can front-run proposer and steal fees from proposer
Audit: 2024-03-taiko-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L114-L116
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProposing.sol#L182


# Vulnerability details

## Impact

When proposing a block with AssignmentHook the proposer pays the prover with ERC20 tokens by approving a transfer to the AssignmentHook. If the block is not using blobs it must be called with an EOA this means that the approval and call to propose must be done in separate transactions and could therefore be front-run.

The prover can front-run the proposer to propose another block but still collect the fee from the previous proposer's coinbase since it has approved the transfer. 

## Proof of Concept

A proposer pays the prover by giving allowance to the AssignmentHook to transfer tokens to the prover
https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/hooks/AssignmentHook.sol#L114-L116

```solidity
            IERC20(assignment.feeToken).safeTransferFrom(
                _meta.coinbase, _blk.assignedProver, proverFee
            )
```

If blobs are not used the `proposeBlock()` must be done with an EOA

https://github.com/code-423n4/2024-03-taiko/blob/f58384f44dbf4c6535264a472322322705133b11/packages/protocol/contracts/L1/libs/LibProposing.sol#L182

```solidity
if (!LibAddress.isSenderEOA()) revert L1_PROPOSER_NOT_EOA();
```

`proposeBlock()` must therefore be called with a EOA and the approval will be done in a separate transaction.

A prover can front-run and use the previous proposers coinbase to steal their fees while proposing a different block. This block could for example be trivial and cheap to prove with ZK e.g a block that only calls the `anchor()`.
## Tools Used
foundry, vscode
## Recommended Mitigation Steps

A potential solution is to always use the `msg.sender` of the `proposeBlock()` call as the coinbase.





## Assessed type

Invalid Validation