Closed code423n4 closed 1 year ago
It is not a call, but opcode compiler simulation. So, it is different from normal call
.
miladpiri marked the issue as sponsor disputed
In contrast to the "expected" behavior of call, the tx can fail due to lack of gas, but the entire tx will revert in those cases (because the call is actually transpiled to another function)
If you can reproduce the bug, please do follow up with me or the Sponsor, but with the info given I must close as invalid
GalloDaSballo marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/L2EthToken.sol#L80 https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/SystemContractHelper.sol#L48
Vulnerability details
Impact
Context
To initiate a withdrawal from L2 to L1, a user can call
L2EthToken.withdraw
method, then funds will be available to calim on L1 viafinalizeEthWithdrawal
method of MailboxFacet.https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/L2EthToken.sol#L80
The mechanism behind this is that the
withdraw
method sends a constructed withdraw message to L1 via L1Messenger'ssendToL1
method, then the user can use the message as a proof in order to claim the funds on L1.https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/L2EthToken.sol#L90-L91
Possible loss of funds
If the withdrawal transaction succeeds, the user should be able to claim his/her funds on L1 always. However, there is a case where the withdrawal transaction succeeds even though the message wasn't sent to L1. This could happen if there is not enough gas during message sending. As a result, the user loses funds since the ether are burnt in L2 but can not be claimed on L1 due to the failure of sending the message to L1.
Note: the
withdraw
method should be called via MsgValueSimulator since msg.value is set by the simulator.Proof of Concept
The execution sequence as follows:
Let's have a look at
SystemContractHelper.toL1
method:https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/SystemContractHelper.sol#L48
There is call to TO_L1_CALL_ADDRESS address, please note that this is just a simulation to zkSync VM-specific v1.3.0 opcodes. For more info, check this: https://github.com/code-423n4/2023-03-zksync/blob/main/docs/VM-specific_v1.3.0_opcodes_simulation.pdf
As noticed, the method should always succeed except when there is not enough gas. In this case, the method could possibly fail silently leading to a successfull withdraw transaciton without having the message sent to L1. Since the gas limit is the minimum of operatorTrustedErgsLimit and gasLimit provided by the user, it is possible the issue occurs if both:
For more details about to_l1_message_opcode, have a look at eraSyncVM relevant code:
Tools Used
Manual analysis
Recommended Mitigation Steps
One possibility is to change the behaviour of to_l1_message_opcode to return a failure in case there wasn't enough gas and then validate it. But this fix would be on a VM level and might not be preferred. Another possibility is to add a check in L1 method to assert there is a minimum gas to cover the messaging as it's critical especially when used for bridging funds from L2 to L1.