Assessment reports>Orderly Network>Critical findings>Possible DOS on cross-chain messages
Category: Business Logic

Possible DOS on cross-chain messages

Critical Severity
Critical Impact
High Likelihood

Description

The LayerZero cross-chain messaging application requires paying a fee for each message sent. The fee is usually paid in the native token of the source chain. Currently, the CrossChainRelayUpgradeable contract does not check the msg.value of the call against the necessary fee required for performing the cross-chain message. This means that an attacker can send a cross-chain message without paying the fee, which leads to the funds being taken from the relayer's account.

function sendMessage(OrderlyCrossChainMessage.MessageV1 memory data, bytes memory payload)
    public
    payable
    override
    onlyCaller
{
    bytes memory lzPayload = data.encodeMessageV1AndPayload(payload);

    uint16 lzDstChainId = _chainIdMapping[data.dstChainId];
    require(lzDstChainId != 0, "CrossChainRelay: invalid dst chain id");

    uint16 version = 1;
    uint256 gasLimit = _flowGasLimitMapping[data.method];
    if (gasLimit == 0) {
        gasLimit = 3000000;
    }
    bytes memory adapterParams = abi.encodePacked(version, gasLimit);

    (uint256 nativeFee,) = lzEndpoint.estimateFees(lzDstChainId, address(this), lzPayload, false, adapterParams);

    _lzSend(
        lzDstChainId, // _dstChainId 
        lzPayload,  // _payload
        payable(address(this)), // _refundAddress @audit why refund to this contract? users could then maybe have 0 as msg.value
        // and send msges with what's already in this contract
        address(0), // _zroPaymentAddress
        adapterParams,  // _adapterParams
        nativeFee // _nativeFee
    );
    emit MessageSent(data, payload);
}

Impact

An attacker may drain the relayer's account by performing a large number of cross-chain messages with msg.value set to zero. This could in turn lead to a temporary denial-of-service attack on the relayer, as they would no longer be able to send cross-chain messages until they replenish their account.

Recommendations

We recommend checking the msg.value of the call against the necessary fee required for performing the cross-chain message.

function sendMessage(OrderlyCrossChainMessage.MessageV1 memory data, bytes memory payload)
    public
    payable
    override
    onlyCaller
{
    // ...

    (uint256 nativeFee,) = lzEndpoint.estimateFees(lzDstChainId, address(this), lzPayload, false, adapterParams);

+   require(msg.value >= nativeFee, "CrossChainRelay: insufficient msg.value");

    // ...
}

Moreover, we also recommend changing the _refundAddress to one of the user's choosing, instead of the contract's address, such that the user can receive the refund of the msg.value if the cross-chain message fails.

function sendMessage(OrderlyCrossChainMessage.MessageV1 memory data, bytes memory payload)
    public
    payable
    override
    onlyCaller
{
    // ...

   _lzSend(
        lzDstChainId, // _dstChainId 
        lzPayload,  // _payload
-       payable(address(this)), // _refundAddress 
+       payable(userAddress), // _refundAddress
        address(0), // _zroPaymentAddress
        adapterParams,  // _adapterParams
        nativeFee // _nativeFee
    );    
}

Remediation

This issue has been acknowledged by Orderly Network, and a fix was implemented in commit 10991618.

Zellic © 2024Back to top ↑