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:
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
A user sends an empty transaction from L2 to L1, with a target that is expecting to receive it.
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.
obront
Medium
L2 to L1 messages to a
fallback()
function will be skippedSummary
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:Additionally, if there is
value
but nodata
, we perform the following:However, in the event that there is no
value
and nodata
, the transaction is skipped entirely (as you can see in thesuccess = 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
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.
Mitigation
If there is no
value
and nodata
, perform the call on L1 as expected.