code-423n4 / 2023-09-maia-findings

25 stars 17 forks source link

uint32 is too small for depositNonce #119

Open c4-submissions opened 1 year ago

c4-submissions commented 1 year ago

Lines of code

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

Vulnerability details

Impact

An attacker can increase the value of depositNonce by calling the callOut function. When depositNonce reaches the maximum value of the uint32, the protocol becomes unavailable and the attacker consumes less than one eth of native gas.

Proof of Concept

depositNonce is a uint32 variable.

    uint32 public depositNonce;

V2 version of BranchBridgeAgent.callOut is a public function, can be called directly, or through BranchRouter to invoke.

    function callOut(
        address payable _refundee,
        bytes calldata _params,
        GasParams calldata _gParams
    ) external payable override lock {
        //Encode Data for cross-chain call.
        bytes memory payload = abi.encodePacked(
            bytes1(0x01),
@>          depositNonce++,
            _params
        );

        //Perform Call
        _performCall(_refundee, payload, _gParams);
    }

    function _performCall(
        address payable _refundee,
        bytes memory _payload,
        GasParams calldata _gParams
    ) internal virtual {
        //Sends message to LayerZero messaging layer
@>      ILayerZeroEndpoint(lzEndpointAddress).send{value: msg.value}(
            rootChainId,
            rootBridgeAgentPath,
            _payload,
            payable(_refundee),
            address(0),
            abi.encodePacked(
                uint16(2),
@>              _gParams.gasLimit,
@>              _gParams.remoteBranchExecutionGas,
                rootBridgeAgentAddress
            )
        );
    }

LayerZero calls the default 200k gas across the chain, but the v2 version can also be modified, in fact, the attacker does not care whether the execution can be successful on the target chain, only the _performCall function can be successfully called. https://layerzero.gitbook.io/docs/evm-guides/advanced/relayer-adapter-parameters

Use the default value of 200k to calculate the gas required to reach the maximum value of the uint32:

2^256 * 200000 = 858993459200000 = 0.86 ether

So the cost to the attacker is very low, especially on L2 networks.

Tools Used

vscode

Recommended Mitigation Steps

depositNonce should be of type uint256.

Assessed type

DoS

c4-pre-sort commented 1 year ago

0xA5DF marked the issue as duplicate of #834

c4-pre-sort commented 1 year ago

0xA5DF marked the issue as sufficient quality report

c4-judge commented 1 year ago

alcueca changed the severity to QA (Quality Assurance)

c4-judge commented 1 year ago

alcueca marked the issue as grade-a

alcueca commented 1 year ago

In your calculation, you assumed a gas cost of 1 wei per gas unit.

To do 2**32 calls at 200k gas each at a 0.1 gwei cost (as in Arbitrum). The cost would be 85,899 ETH, and MaiaDAO can just redeploy the BridgeAgent to reset the nonce back to zero.

Think about the cost of making 4.2 billion transactions on Arbritrum, and you'll see it more clearly.