Open github-actions[bot] opened 1 year ago
Comment from Optimism
Description: Causing users lose their fund during finalizing withdrawal transaction
Reason: This is a very creative exploit that takes advantage of the L1CrossDomainMessenger
's reentrancy guard on relayMessage
. By creating an attack contract that reverts the first time a message is relayed by the OptimismPortal
to the L1CrossDomainMessenger
, but can be toggled to call finalizeWithdrawalTransaction
for a different withdrawal transaction when the attacker replays it via the L1CrossDomainMessenger
, the attacker can force the honest withdrawal's call to the L1CrossDomainMessenger
to revert, effectively bricking it. PoC: here
Action: If a withdrawal transaction fails, we should check to see if the _target
of the WithdrawalTransaction
is the L1CrossDomainMessenger
. If so, check if the call's revert data is the reentrancy guard message. If it is, we should revert the finalizeWithdrawalTransaction
message and NOT mark the withdrawal as finalized.
HE1M
high
Causing users lose their fund during finalizing withdrawal transaction
Summary
A malicious user can make users lose their fund during finalizing their withdrawal. This is possible due to presence of reentrancy guard on the function
relayMessage
.Vulnerability Detail
AttackContract
) on L1.struct WithdrawalTransaction { uint256 nonce; address sender; address target; uint256 value; uint256 gasLimit; bytes data; }
interface IOptimismPortal { function finalizeWithdrawalTransaction(WithdrawalTransaction memory _tx) external; }
contract AttackContract { bool public donotRevert; bytes metaData; address optimismPortalAddress;
}
enableRevert
to setdonotRevert
totrue
. So that if later the functionattack()
is called again, it will not revert.setMetaData
on the contractAttackContract
with the following parameter:_tx
= Alice's withdrawal transactionmetaData
will be equal tofinalizeWithdrawalTransaction.selector
+ Alice's withdrawal transaction.finalizeWithdrawalTransaction
is called by anyone (Alice), Bob calls the functionrelayMessage
with the required data to retry his previous failed message again.donotRevert
istrue
, the call to functionattack()
will not revert, instead the body ofelse clause
will be executed.else clause
, it calls the functionfinalizeWithdrawalTransaction
with Alice's withdrawal transaction as the parameter, to finalize Alice's transaction. https://github.com/sherlock-audit/2023-01-optimism/blob/main/optimism/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L243relayMessage
in the contractCrossDomainMessanger
. https://github.com/sherlock-audit/2023-01-optimism/blob/main/optimism/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L324CrossDomainMessenger.relayMessage
==>AttackContract.attack
==>OptimismPortal.finalizeWithdrawalTransaction
=>CrossDomainMessenger.relayMessage
In summary the attack is as follows:
AttackContract
.AttackContract.attack
on L1.AttackContract.attack
will be called.AttackContract.attack
reverts. So, Bob's message will be flagged as failed message.AttackContract.donotRevert
to true.CrossDomainMessenger.relayMessage
will call theAttackContract.attack
, then it callsOptimismPortal.finalizeWithdrawalTransaction
to finalize innocent user's withdrawal transaction. Then, it callsCrossDomainMessenger.relayMessage
, but it will be unsuccessful because of reentrancy guard.Impact
By doing this attack it is possible to prevent users from withdrawing their fund. Moreover, they lose their fund because withdrawal is flagged as finalized, but the withdrawal sent to
L1CrossDomainMessanger
was not successful.Code Snippet
Tool used
Manual Review
Recommendation
Maybe it is better to use the following code instead of: https://github.com/sherlock-audit/2023-01-optimism/blob/main/optimism/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L324-L329