Platform: Code4rena
Start Date: 10/05/2024
Pot Size: $300,500 USDC
Total HM: 4
Participants: 27
Period: 17 days
Judge: Picodes
Total Solo HM: 1
Id: 375
League: ETH
Rank: 26/27
Findings: 1
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: Audinarey, Dup1337, K42, KupiaSec, LessDupes, Rhaydden, SpicyMeatball, Takarez, ZanyBonzy, bronze_pickaxe, carlitox477, dontonka, forgebyola, fyamf, guhu95, hihen, josephdara, ladboy233, slvDev, twcctop, xuwinnie, zanderbyte
0 USDC - $0.00
The function MerkleTreeLib.root
is used to calculate the root of Merkle tree expansions.
It does so by incrementally hashing the expansion elements from the lowest-order to the highest-order elements.
However, in its loop, the state of the calculation accum
is left unchanged until the first non-zero subtree is found, and this means that the number of leading zeros in the expansion doesn't influence the root calculation at all.
For example, the tree [bytes32(uint(0)), bytes32(uint(1))]
has the same root as [bytes32(uint(1))]
and [bytes32(uint(0)), bytes32(uint(0)), bytes32(uint(0)), bytes32(uint(1))]
.
While this is in general not a problem, it can be more problematic when dealing with Merkle trees having an even number of elements which are known.
Since such Merkle trees have an even number of elements, their first element is zero, so the full expansion can be shifted left without changing the root.
These left-shifted Merkle trees could theoretically pass the verifyInclusionProof
check logic because:
proof
because by knowing the elements of the original expansion, they know the preimage of elements that build up to the legitimate rootThere doesn't seem to be any way to directly exploit this issue because all practical usages of expansions within the protocol scope have odd numbers of elements, however, it is warmly recommended to also hash the leading zeros when computing the Merkle trees to have an in-depth defense against this kind of attack.
According to the documentation and technical deep dive, an overflow assertion should refer to the assertion that follows one where the block limit was reached but not all messages were processed. However, in the implementation within the createNewAssertion()
function, the flag for an overflow assertion is set during the creation of the overflowing assertion itself, not the subsequent one. This discrepancy leads to a bypass of the 1-hour delay, applying instead to the assertion that actually overflows.
Relevant code snippet from createNewAssertion()
:
if (assertion.afterState.machineStatus != MachineStatus.ERRORED && afterStateCmpMaxInbox < 0) { overflowAssertion = true; ... }
Manual review
To align the implementation with the intended functionality as described in the documentation, the handling of the overflow assertion flag should be adjusted. The flag should be set for the assertion following the one that actually overflows, not during the creation of the overflowing assertion itself.
#0 - gzeoneth
2024-05-31T20:07:45Z
For L-01: The tree does not allow for empty leaves to be added, see: https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/challengeV2/libraries/MerkleTreeLib.sol#L241 and https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/challengeV2/libraries/MerkleTreeLib.sol#L159C9-L160C1
But I think it can be QA as it might be nice to add more comments about this. Since it is technically possible to build a tree with empty leaves outside of this contract and unknowingly use the methods in this lib to verify proofs etc.
#1 - gzeoneth
2024-05-31T20:08:27Z
For L-02: Invalid. Expected behavior. Time delta is expected to apply to the last assertion in the series of overflow assertions because that is what determine the inbox position of the next assertion afterward.
#2 - c4-judge
2024-06-10T17:24:14Z
Picodes marked the issue as grade-b