This repository has been archived by the owner on May 26, 2023. It is now read-only.
obront - Withdrawals with high gas limits can be bricked by a malicious user, permanently locking funds #96
Labels
Has Duplicates
A valid issue with 1+ other issues describing the same vulnerability
High
A valid High severity issue
Reward
A payout will be made for this issue
obront
high
Withdrawals with high gas limits can be bricked by a malicious user, permanently locking funds
Summary
Transactions to execute a withdrawal from the Optimism Portal require the caller to send enough gas to cover
gasLimit
specified by the withdrawer.Because the EVM limits the total gas forwarded on to 63/64ths of the total
gasleft()
(and silently reduces it to this value if we try to send more) there are situations where transactions with high gas limits will be vulnerable to being reverted.Because there are no replays on this contract, the result is that a malicious user can call
finalizeWithdrawalTransaction()
with a precise amount of gas, cause the withdrawer’s withdrawal to fail, and permanently lock their funds.Vulnerability Detail
Withdrawals can be withdrawn from L2's
L2ToL1MessagePasser
contract to L1'sOptimismPortal
contract. This is a less "user-friendly" withdrawal path, presumably for users who know what they are doing.One of the quirks of the
OptimismPortal
is that there is no replaying of transactions. If a transaction fails, it will simply fail, and all ETH associated with it will remain in theOptimismPortal
contract. Users have been warned of this and understand the risks, so Optimism takes no responsibility for user error.In order to ensure that failed transactions can only happen at the fault of the user, the contract implements a check to ensure that the gasLimit is sufficient:
When the transaction is executed, the contract requests to send along all the remaining gas, minus the hardcoded
FINALIZE_GAS_BUFFER
for actions after the call. The goal is that this will ensure that the amount of gas forwarded on is at least the gas limit specified by the user.Optimism is aware of the importance of this property being correct when they write in the comments:
The issue is that the EVM specifies the maximum gas that can be sent to an external call as 63/64ths of the
gasleft()
. For very large gas limits, this 1/64th that remains could be greater than the hardcoded FINALIZE_GAS_BUFFER value. In this case, less gas would be forwarded along than was directed by the contract.Here is a quick overview of the math:
X * 64 / 63
gas to be available at the time the function is called.X + 20_000
gas a few operations prior to the call (which guarantees that we haveX + 14878
at the time of the call).X / 64 > 14878
(in other words, when the amount of gas sent is greater than952_192
), the caller is able to send an amount of gas that passes the check, but doesn't forward the required amount on in the call.Impact
For any withdrawal with a gas limit of at least 952,192, a malicious user can call
finalizeWithdrawalTransaction()
with an amount of gas that will pass the checks, but will end up forwarding along less gas than was specified by the user.The result is that the withdrawing user can have their funds permanently locked in the
OptimismPortal
contract.Proof of Concept
To test this behavior in a sandboxed environment, you can copy the following proof of concept.
Here are three simple contracts that replicate the behavior of the Portal, as well as an external contract that uses a predefined amount of gas.
(Note that we added 5122 to the gas included in the call to correct for the other bug we submitted, as this issue remains even when the other bug is patched.)
Here is a Foundry test that calls the Portal with various gas values to expose this vulnerability:
As you can see:
X
from our formula above.11_245_655 * 64 / 63 = 11_424_157
gas available at the time the call is made.9023
gas before it makes our call, so we can see that if we send11_424_157 + 9_023 = 11_433_180
gas, the test passes.11_266_734
gas, the total gas will be small enough to fail the require check.Code Snippet
https://github.com/sherlock-audit/2023-01-optimism/blob/main/optimism/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L310-L329
https://github.com/sherlock-audit/2023-01-optimism/blob/main/optimism/packages/contracts-bedrock/contracts/libraries/SafeCall.sol#L17-L36
Tool used
Manual Review
Recommendation
Change the check to account for this 63/64 rule:
The text was updated successfully, but these errors were encountered: