Platform: Code4rena
Start Date: 02/10/2023
Pot Size: $1,100,000 USDC
Total HM: 28
Participants: 64
Period: 21 days
Judge: GalloDaSballo
Total Solo HM: 13
Id: 292
League: ETH
Rank: 44/64
Findings: 1
Award: $656.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
656.3255 USDC - $656.33
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L236-L273 https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L275-L281 https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L176-L212
When transferring ETH from L1 to L2, the Diamonds "Mailbox" facet has a function that verifies the amount against a predefined limit using the msg.value attribute. If the msg.value exceeds the allowed threshold, and the transaction on L2 reverts, the bridged ETH is refunded to the address designated as the refundRecipient.
Now, the loophole emerges with the ERC20L1Bridge. When users bridge ERC20 tokens, they are required to attach ETH (as msg.value) to cover gas fees on L2, even if they aren't explicitly bridging any ETH. Technically, the Bridge contract invokes the requestL2Transaction function on the Diamond on behalf of the user. As a result, within the context of this function, the msg.sender is recognized as the ERC20L1Bridge, not the originating user.
Here's the exploit: if a user has already reached their ETH bridging limit, they can still initiate a token transfer via the ERC20L1Bridge, intentionally attaching a higher msg.value than necessary. When specifying the refundRecipient, they can use their L2 withdrawal address. Due to the call structure and the misidentification of the msg.sender, the Diamonds "Mailbox" facet's limit check doesn't recognize the overage in msg.value.
In essence, this allows users to bypass the deposit restrictions by masking excessive ETH deposits within ERC20 transfers, effectively sidestepping the controls set by the Diamonds "Mailbox" facet.
Assume the ETH bridge deposit limit in Diamond for every user is 100 ETH. Assume Alice already bridged 100ETH hence, she can't bridge nor interact with the Diamond because of this check: https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L275-L281
Now, Alice can bridge 1 wei of DAI using L1ERC20Bridge and sends 50 Ether as "msg.value" and also the "refundRecipient" will be Alice's desired withdrawal address that will receive the excess ETH in L2.
When L1ERC20 bridge calls the Diamond as follows: https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L198-L206 it forwards all the "msg.value" to Diamond. And remember, the "msg.sender" in the "requestL2Transaction" function inside the Diamond will be the L1Bridge, not Alice !
As we can see in the following snippet where L1Bridge called the Diamonds "requestL2Transaction" the "msg.sender" is the L1ERC20Bridge and the deposit limit for the bridge is checked instead of Alice. This way Alice will be able to bridge the 50ETH because the bootloader will refund the excess ETH that Alice send to Alice's picked refund address at L2. https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L261
Other
#0 - c4-pre-sort
2023-11-02T15:36:37Z
141345 marked the issue as duplicate of #246
#1 - c4-judge
2023-11-24T19:22:54Z
GalloDaSballo marked the issue as satisfactory