Type confusion between Withdrawal
and Cancel
actions
Description
After the updater invokes update_l1_from_l2
to update the Merkle tree root for a given range, anyone can call close_withdrawal
or close_cancel
to finalize processing of a pending request, perfecting the withdrawal or cancellation according to the corresponding entry in the Merkle tree.
Merkle trees store both Withdrawal
and Cancel
structs, and the leaf hash function is implemented as keccak256(abi.encode(<struct>))
; it does not distinguish between Cancel
and Withdrawal
nodes with a distinct prefix.
The two data types have identical sizes, and their fields overlap as follows:
Cancel struct | Type | Withdrawal struct | Type |
---|---|---|---|
requestId | RequestId | requestId | RequestId |
Range.start | uint256 | recipient | address |
Range.end | uint256 | tokenAddress | address |
hash | bytes32 | amount | uint256 |
It is possible to submit a call to close_cancel
using the Withdrawal
struct intended to be provided to close_withdrawal
. The Merkle inclusion check would pass because the ABI encoding and therefore the leaf hash are the same. The request would be marked as canceled, preventing the withdrawal with the given ID from being finalized.
Impact
An attacker could race the call to close_withdrawal
and call close_cancel
using the encoding of the Withdrawal
struct and the same merkle_root
and proof that would be supplied to close_withdrawal
. This would lead to a denial of service and loss of funds.
Recommendations
Use a distinct prefix for all different types of Merkle tree leaves.
We strongly recommend to also differentiate leaves from intermediate nodes. While the current implementation does not seem to be exploitable due to the Merkle tree proof verification functions receiving the depth of the tree as input, this is a common source for vulnerabilities in Merkle tree implementations.
Remediation
This issue has been acknowledged by Gasp, and a fix was implemented in commit 47d82117↗.
The commit introduces separate hashing functions for each leaf type which prepend a unique prefix. This prevents collisions between hashes for different leaf types.