sherlock-audit / 2024-08-tokamak-network-judging

1 stars 0 forks source link

obront - L2 to L1 messages to a `fallback()` function will be skipped #44

Open sherlock-admin3 opened 1 month ago

sherlock-admin3 commented 1 month ago

obront

Medium

L2 to L1 messages to a fallback() function will be skipped

Summary

When a withdrawal message is sent from L2 to L1 that does not contain any data or value, the call is skipped instead of being executed. While the most common reasons for a transaction are to send value or data, this precludes the ability to call a contract's fallback() function, which should be expected to work.

Root Cause

In OptimismPortal2.sol, a finalized transaction is performed with the following logic:

if (_tx.data.length != 0) {
    success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
} else {
    success = true;
}

Additionally, if there is value but no data, we perform the following:

IERC20(_nativeTokenAddress).safeTransfer(_tx.target, _tx.value);

However, in the event that there is no value and no data, the transaction is skipped entirely (as you can see in the success = true above).

While it is rare that such a transaction will be sent through the bridge, the inability for a valid bridged transaction to be processed on L1 could cause problems.

Internal Preconditions

None

External Preconditions

None

Attack Path

  1. A user sends an empty transaction from L2 to L1, with a target that is expecting to receive it.
  2. After finalizeWithdrawalTransaction() is called, nothing happens.

Impact

Users can send L2 to L1 messages that comply with the bridge's rules and will simply not be executed.

PoC

Here is a dummy example of a contract that expects an empty call from L2 in order to start some process. While the L2 portion of the transaction would work fine, the L1 execution would be skipped, and the contract could not be started.

contract L2Listener {
    fallback() external {
        require(msg.sender == portal, "portal only");
        require(msg.data.length == 0, "not trying to call another function");

        started = true;
    }
}

Mitigation

If there is no value and no data, perform the call on L1 as expected.