xanoutas/solidity-security-findings huggingface dataset:
Vulnerability of fund draining by an attacker providing their own refund address.
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L120
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L823


# Vulnerability details

## Impact
The impact is loss of funds in the contract, which could disrupt operations or cause financial losses.

## Proof of Concept
**The problematic code is in the _performCall internal function:**

// @notice Constructor for Bridge Agent.
constructor(
  uint16 _localChainId,
  address _lzEndpointAddress,
  address _localPortAddress,
  address _localRouterAddress    
) {
  // ...

  bridgeAgentExecutorAddress = DeployRootBridgeAgentExecutor.deploy(address(this));

  // ...
}


function _performCall(
  uint16 _dstChainId,
  address payable _refundee,
  bytes memory _payload,
  GasParams calldata _gParams
) internal {
  // ...

  // Check if call to local chain
  } else {
    // Send Gas to Local Branch Bridge Agent
    callee.call{value: msg.value}("");
  
  // ...
}

**This sends the entire msg.value to the provided _refundee address if the destination chain ID _dstChainId is the same as the local chain ID.**

**An attacker could exploit this by:**

**- Calling a function that uses _performCall internally, like callOutAndBridge, and providing a large msg.value.**
**- Setting the _dstChainId parameter to the local chain ID.**
**Providing their own address as _refundee.**

**This would result in the entire msg.value being sent to the attacker's address rather than the intended recipient.**

## Tools Used
Manual
## Recommended Mitigation Steps
**the _performCall function should only send the exact amount of ETH needed for gas to the _refundee address when on the local chain. The rest should remain in the contract.**


## Assessed type

Other
encode-packed-collision/Denial Of Service
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L940-L947
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L1045-L1114


# Vulnerability details

## Impact

Data Corruption

Denial Of Service

When exploited by an attacker the 'encode-packed-collision' vulnerability can allow them to manipulate the data in the smart contract potentially leading to unauthorized access to funds or other sensitive information.

To exploit this vulnerability an attacker can use 'abi.encodePacked' with two dynamic arguments (string/bytes), which can result in hash collisions. 

This can allow the attacker to manipulate the data in the smart contract, potentially leading to unauthorized access to funds or other sensitive information.

The PoC exploit provided below shows just how an attacker can use this attack against a vulnerable smart contract.


## Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FirstContract {
    function foo(bytes memory _data) public pure returns (uint256) {
        // Some logic here, but it doesn't matter for the exploit
        return _data.length;
    }
}

contract SecondContract {
    FirstContract firstContract;

    constructor(address _firstContract) {
        firstContract = FirstContract(_firstContract);
    }

    function bar(bytes memory _data) public view returns (uint256) {
        // This function takes encoded data and calls a function in the FirstContract
        return firstContract.foo(_data);
    }
}





## Tools Used

Manual Review
Slither
VS Code 
Remix IDE

## Recommended Mitigation Steps


To mitigate the memory corruption vulnerabilities caused by the abi.encodePacked() function, developers should follow these best practices:

Use abi.encode() instead of abi.encodePacked(): 

The abi.encode() function applies ABI encoding rules which ensures that all elementary types are padded to 32 bytes and dynamic arrays include their length. 
This helps prevent encode-packed collision issues and memory corruption vulnerabilities.

Use keccak256() to generate a unique hash for the payload: 

The keccak256() function can be used to generate a unique hash for the payload which can help prevent encode-packed collision issues and memory corruption vulnerabilities.

Use the memory keyword for the payload variable: 

The memory keyword can be used to specify that the payload variable should be stored in memory which can help prevent memory corruption vulnerabilities.

Avoid encoding multiple dynamic arrays: 

Encoding multiple dynamic arrays can cause encode-packed collision issues and memory corruption vulnerabilities. Developers should avoid encoding multiple dynamic arrays or ensure that the arrays have the same length.

By following these best practices developers can mitigate the memory corruption vulnerabilities caused by the abi.encodePacked() functio
potential vulnerability where retrieving a settlement can drain unrelated funds from the contract.
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L295
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L823
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L808-L839
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootBridgeAgent.sol#L274-L296


# Vulnerability details

## Impact
An attacker could:
1. Deposit funds into the contract unrelated to any settlement
2. Initiate a fake settlement to themselves with a small amount
3. Call retrieveSettlement to drain all funds in the contract

## Proof of Concept
**In the retrieveSettlement function, it calls _performCall and passes address(this).balance as the msg.value. This sends all available funds in the contract, not just the funds related to that specific settlement.**


function retrieveSettlement(uint32 _settlementNonce, GasParams calldata _gParams) external payable lock {
  //...

  //Perform Call
  _performCall(settlementReference.dstChainId, payable(settlementOwner), payload, _gParams); 
}

function _performCall(
        uint16 _dstChainId,
        address payable _refundee,
        bytes memory _payload,
        GasParams calldata _gParams
    ) internal {
  //...

  ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}(
        //...
  )
}

**The retrieveSettlement function allows the settlement owner to retrieve their funds after a failed settlement transaction. It takes the settlement nonce as a parameter, gets the settlement info from storage, and checks that the settlement is eligible for retrieving.**

**The issue is that it then performs the cross-chain call to retrieve the funds by calling the _performCall internal function, passing the settlement owner's address as the _refundee parameter.**

**The _performCall function sends all the funds in the contract (address(this).balance) when making the cross-chain call. This means that if there are extra funds in the contract unrelated to the settlement, they will get sent to the settlement owner when they call retrieveSettlement.**

**In summary**

**When retrieveSettlement() is called, it performs a cross-chain call to the destination chain using _performCall(). _performCall() forwards all funds in the contract to the destination using:**


ILayerZeroEndpoint(lzEndpointAddress).send{value: address(this).balance}(...)

**This means that any ETH or tokens unrelated to the settlement can be drained when retrieving.**




## Tools Used
Manual
## Recommended Mitigation Steps
**To mitigate this, the value transferred should be limited to the settlement amount, not the entire contract balance.
For example:**


function retrieveSettlement(uint32 _settlementNonce, GasParams calldata _gParams) external payable lock {

  Settlement storage settlement = getSettlement[_settlementNonc
RootBridgeAgent#_performFallbackCall   refund addresss  may not correct . 
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/90a2bda6f00b588e607344d88eae2810dbd6c443/src/RootBridgeAgent.sol#L938-L948
https://github.com/code-423n4/2023-09-maia/blob/746905cdd3aa165ba1f9274c360dfd9f0c52e781/src/BranchBridgeAgent.sol#L422-L431


# Vulnerability details

## Impact
The `RootBridgeAgent#_performFallbackCall` refund address may be incorrect, potentially causing fund loss in `RootBridgeAgent`.

## Proof of Concept
In `RootBridgeAgent`, the function `lzReceive` processes messages from layer0. The procedure called is listed below, with `_performFallbackCall` as an example:

1. `lzReceive`
2. `lzReceiveNonBlocking`
3. In `lzReceiveNonBlocking`, if `_payload == 0x08` -> `_performFallbackCall`. In this step, notice the input parameter `_payload[PARAMS_START:PARAMS_START_SIGNED]`. This is wrapped in `BranchBridgeAgent#retrieveDeposit`, and it's `msg.sender` who calls `retrieveDeposit`.
4. `layer0.send()`, and the refund address is `msg.sender` who calls `retrieveDeposit` in `BranchBridgeAgent`.

The issue is in `RootBridgeAgent#_performFallbackCall`, where the layer0 send refund address is in another chain call. Due to the lack of limitations in `BranchBridgeAgent#retrieveDeposit`, a malicious user could call `retrieveDeposit` an infinite number of times to drain the native token left in `RootBridgeAgent`.





Poc: 
I only mock the situation of calling retrieve deposit multiple times.Add this script in  `RootTest.t.sol`

```solidity
// forge test --mt testCallRetriveMultipulTimes -vv
    function testCallRetriveMultipulTimes()  public {

        // Set up
        test_tt_AddLocalTokenArbitrum();

        // Prepare data
        bytes memory packedData;

        {
            Multicall2.Call[] memory calls = new Multicall2.Call[](1);

            // Mock Omnichain dApp call
            calls[0] = Multicall2.Call({
                target: newAvaxAssetGlobalAddress,
                callData: abi.encodeWithSelector(bytes4(0xa9059cbb), mockApp, 0 ether)
            });

            // Output Params
            OutputParams memory outputParams = OutputParams(address(this), newAvaxAssetGlobalAddress, 150 ether, 0);

            // RLP Encode Calldata Call with no gas to bridge out and we top up.
            bytes memory data = abi.encode(calls, outputParams, avaxChainId);

            // Pack FuncId
            packedData = abi.encodePacked(bytes1(0x02), data);
        }

        address _user = address(this);

        // Get some gas.
        hevm.deal(_user, 1 ether);

        // Assure there is enough balance for mock action
        hevm.prank(address(rootPort));
        ERC20hTokenRoot(newAvaxAssetGlobalAddress).mint(address(rootPort), 50 ether, rootChainId);
        hevm.prank(address(avaxPort));
        ERC20hTokenBranch(avaxMockAssethToken).mint(_user, 50 ether);

        // Mint Underlying Token.
        avaxMockAssetToken.mint(_user, 100 ether);

        // Prepare deposit info
        DepositInput memory depositInpu
Due to an error in the BranchBridgeAgent.retrySettlement() function, the user can repeat the call to the Omnichain root environment, repeating the deposit without spending on their part
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464-L478


# Vulnerability details

## Proof of Concept
BranchBridgeAgent.retrySettlement(https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464-L478) () - external function to retry a failed Settlement entry on the root chain.

However, the user can repeat the call to the Omnichain root environment, repeating the deposit without spending on their part. This can happen because the retrySettlement() function does not check that the deposit status is already deposit.status = STATUS_SUCCESS. Those. the function does not check at the very beginning that the deposit has already been successfully completed.

This will result in the user being able to repeat the call to the Omnichain root environment, repeating the deposit without spending on their part.

## Tools Used
Manual review

## Recommended Mitigation Steps
Check at the beginning of the function that the deposit has not yet been made


## Assessed type

Access Control
An attacker can pocket the funds by repeating an unsuccessful transaction to the root chain
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L471
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464-L478


# Vulnerability details

## Proof of Concept
BranchBridgeAgent.retrySettlement() (https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464-L478) - external function to retry a failed Settlement entry on the root chain. The function is of type external and can be called by anyone.

An attacker can call BranchBridgeAgent.retrySettlement() by retrying the failed transaction to the root chain. In this case, _performCall() will be called with the attacker’s address in the payload:

bytes memory params = abi.encode(_settlementNonce, msg.sender, _params, _gParams[1]);
bytes memory payload = abi.encodePacked(_hasFallbackToggled ? bytes1(0x87) : bytes1(0x07), params);

If the transaction is successful, the attacker will be able to appropriate the funds for himself.

## Tools Used
Manual review

## Recommended Mitigation Steps
Set a check that only the owner of _settlementNonce can call the function:
if (deposit.owner != msg.sender) revert NotDepositOwner();


## Assessed type

Access Control
An attacker can steal gas refunds by repeating an unsuccessful transaction to the root chain
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L477
https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464-L478


# Vulnerability details

## Proof of Concept
BranchBridgeAgent.retrySettlement() (https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L464-L478) - external function to retry a failed Settlement entry on the root chain. The function is of type external and can be called by anyone.

An attacker can call BranchBridgeAgent.retrySettlement() by retrying the failed transaction to the root chain. In this case, _performCall() will be called with the attacker’s address:

_performCall(payable(msg.sender), payload, _gParams[0]);

If the transaction is successful, the attacker will be able to receive a refund for the transaction gas.

## Tools Used
Manual review

## Recommended Mitigation Steps
Set the owner's address to _performCall


## Assessed type

Access Control
Due to an error in the BranchBridgeAgent.retryDeposit() function, the user can repeat the call to the Omnichain root environment, repeating the deposit without spending on their part
Audit: 2023-09-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L343-L419


# Vulnerability details

## Proof of Concept
BranchBridgeAgent.retryDeposit(https://github.com/code-423n4/2023-09-maia/blob/main/src/BranchBridgeAgent.sol#L343-L419) () - function to perform a call to the Root Omnichain Environment retrying a failed deposit that hasn't been executed yet.

However, the user can repeat the call to the Omnichain root environment, repeating the deposit without spending on their part. This can happen because the retryDeposit() function does not check that the deposit status is already deposit.status = STATUS_SUCCESS. Those. the function does not check at the very beginning that the deposit has already been successfully completed.

This will result in the user being able to repeat the call to the Omnichain root environment, repeating the deposit without spending on their part.

## Tools Used
Manual review

## Recommended Mitigation Steps
Check at the beginning of the function that the deposit has not yet been made


## Assessed type

Access Control
[M-17] Reentrancy in the BranchBridgeAgent contract
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L161-L173
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L494-L571
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L578-L584


# Vulnerability details

## Impact
In a Re-entrancy attack, a malicious contract calls back into the calling contract before the first invocation of the function is finished. 
This may cause the different invocations of the function to interact in undesirable ways, especially in cases where the function is updating state variables after the external calls.
This may lead to loss of funds, improper value updates, token loss, etc.
## Proof of Concept
**The getFeeEstimate function is vulnerable to reentrnacy**
```sol
// Line 161-173
    function getFeeEstimate(uint256 _gasLimit, uint256 _remoteBranchExecutionGas, bytes calldata _payload)
        external
        view
        returns (uint256 _fee)
    {
        (_fee,) = ILayerZeroEndpoint(lzEndpointAddress).estimateFees(
            rootChainId,
            address(this),
            _payload,
            false,
            abi.encodePacked(uint16(2), _gasLimit, _remoteBranchExecutionGas, rootBridgeAgentAddress)
        );
    }
```
**The clearTokens function is vulnerable to reentrancy**
```sol
// Line 494-571
    function clearTokens(bytes calldata _sParams, address _recipient)
        external
        override
        requiresAgentExecutor
        returns (SettlementMultipleParams memory)
    {
        // Parse Tokens Length
        uint8 numOfAssets = uint8(bytes1(_sParams[0]));


        // Parse Nonce
        uint32 nonce = uint32(bytes4(_sParams[PARAMS_START:PARAMS_TKN_START]));


        // Initialize Arrays
        address[] memory _hTokens = new address[](numOfAssets);
        address[] memory _tokens = new address[](numOfAssets);
        uint256[] memory _amounts = new uint256[](numOfAssets);
        uint256[] memory _deposits = new uint256[](numOfAssets);


        // Transfer the token to the recipient
        for (uint256 i = 0; i < numOfAssets;) {
            // Cache common offset
            uint256 currentIterationOffset = PARAMS_START + i;


            // Parse Params
            _hTokens[i] = address(
                uint160(
                    bytes20(
                        bytes32(
                            _sParams[
                                PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * i) + ADDRESS_END_OFFSET:
                                    PARAMS_TKN_START + (PARAMS_ENTRY_SIZE * currentIterationOffset)
                            ]
                        )
                    )
                )
            );


            _tokens[i] = address(
                uint160(
                    bytes20(
                        bytes32(
                            _sParams[
USDT tokens should approve to zero first otherwise it may cause other tokens to get stuck in the case of aprooveMultipleTokens
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BaseBranchRouter.sol#L160-L176


# Vulnerability details

## Impact

Unsafe ERC20 approve that do not handle non-standard erc20 behavior. Some token contracts do not return any value. Some token contracts revert the transaction when the allowance is not zero.

## Proof of Concept

When receiving tokens to the destination chain, the token attempts to approve the destination port address.

```
        if (_deposit > 0) {
            _token.safeTransferFrom(msg.sender, address(this), _deposit);
            ERC20(_token).approve(_localPortAddress, _deposit);
        }
```

For a token like USDT, the token needs to be approved to zero first before calling approve. This may cause a problem in the case of multiple token transfers, and because usdt is not approve, the other tokens may be stuck in the transfer process

Since the omnichain uses all sorts of tokens, it is important that the weird ERC20 tokens are counted as well.

## Tools Used

VSCode

## Recommended Mitigation Steps

It is recommended to set the allowance to zero before increasing the allowance and use safeApprove/safeIncreaseAllowance.





## Assessed type

Other
ChainLink should be used as an Oracle for messaging instead of Google Cloud 
Audit: 2023-09-maia-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-maia/blob/c0dc3550e0754571b82d7bfd8f0282ac8fa5e42f/src/BranchBridgeAgent.sol#L45


# Vulnerability details




## Impact
Each User Application contract (e.g. BranchBidgeAgent) built on LayerZero will work without configuration using defaults, but a UA will also be able to configure its own. Maia intends to use the default config. However, Google Cloud Oracle is the default as of 9/19/23. 

Two issues from this:
1. Google Cloud is centralised (I guess no need to elaborate this)
2. Google Cloud Oracle is still new and not mature enough (from a security perspective) for such a crucial functionality (cross-chain messaging).



## Proof of Concept

Please check https://layerzero.gitbook.io/docs/ecosystem/oracle


## Tools Used
Manual analysis

## Recommended Mitigation Steps

ChainLink should be used instead as it is at least not really centralised. I understand that Maia would like to use the default config. However, this is still possible, just implement your own config and only change the oracle address. Keep everything else as default from LayerZero.







## Assessed type

Oracle