Open howlbot-integration[bot] opened 1 month ago
The force inclusion delay buffer feature prevents this type of repeated censorship by the sequencer.
From the doc and the whitepaper the 7-day delay is intended for finalization on L1, so to me this is at most a misconfiguration issue, so falls within QA
Picodes changed the severity to QA (Quality Assurance)
Picodes marked the issue as grade-b
Lines of code
https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/main/src/bridge/SequencerInbox.sol#L314 https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/main/src/challengeV2/libraries/ChallengeEdgeLib.sol#L250-L251
Vulnerability details
Cause
The BOLD dispute protocol requires multiple turn-based steps. When deployed on L2 for enabling the L3 use-case, the L2 sequencer can exploit the L3. It can do that by censoring honest validators dispute moves and forcing them to use the L1 force inclusion mechanism. This allows the sequencer to attack or collude with an attacker to create invalid assertions for L3 chains settling to L2. By causing honest L3 validators to use the L1 force inclusion mechanism, the transactions will be included on L2 via L1 with a delay of up to 24 hours for each turn. As the dispute requires many tens of turns (around 50) to complete, these accumulated delays will cause the honest validators to lose.
Impact
The sequencer can submit arbitrary withdrawals and drain any L3 deployed on L2. While generally the sequencer should not be able to cause any safety violations for the sequenced chain, in this case, they can use their limited censorship ability to violate the safety of any L3 by preventing the timely progress of its dispute games.
The dispute requires around 50 turn-based steps from an honest participant, each delayed by possibly up to 24 hours. Because each participant is limited to a 7-day chess clock, honest participants can be forced to lose due to the L1 inclusion delays. This allows the attacker to be able to confirm an arbitrary assertion and prove arbitrary withdrawals from all L3 chains using BOLD on L2.
Notably, even if the maximum or average delay can be reduced, with 50 turn based steps, even if it's shortened to only 4 hours, the 7 day clock will be exhausted by the delays alone. Moreover, even if the average delay can be reduced to even 2 hours, this will leave the honest validators much less time than needed to reliably guarantee the safety of the chain according to the assumptions of the protocol.
L3s BOLD deployments are in scope
While this only is relevant for L3s using BOLD on L2s (Arbitrum itself), and not for L2s using BOLD on L1, the scope of this contest clearly includes the L3 use case:
RollupCore
andRollupAdmin
code setting_assertionCreatedAtArbSysBlock
and_hostChainIsArbitrum
usingArbitrumChecker.runningOnArbitrum()
that differentiates behavior and storage between deployments on L1 and L2 (rollups on Arbitrum itself - L3s) indicating the code is intended for both L1 and L2 deployment.Severity
Because the sequencer is not trusted with safety with L3s, and is not trusted within the scope of fault proofs, the severity is high. This is not only true due to the trust guarantees, but also due to the intention to decentralize the sequencer role. The loss of funds and trust in the system would be catastrophic, due to the total loss of funds that the L2 sequencer operator is capable of inflicting upon the L3s.
Proof of Concept
As can be seen in the
testCanConfirmByOneStep
test which runs a challenge from start to finish, if a dishonest assertion is made, the honest validator must complete the full dispute.The full dispute involves 6 levels: block level, then 4 intermediate levels, and a final SmallStep level. Additionally, a final
confirmEdgeByOneStepProof
must be called to prove the final small step single instruction on-chain to prevent the dishonest edge from being confirmed by time and its assertion being confirmed. This amounts to 6 levels and 1 additional call.For each level, multiple interactions are needed, each can only be done after the opponent's move:
createLayerZeroEdge
to dispute the claim, and then severalbisectEdge
calls. Crucially,bisectEdge
calls are done in turns because each bisected edge has to be "rivaled" before being further bisected until an edge of length one is reached for that level. Thus, it's not possible for the honest challenger to fully bisect their claim ahead of time (in a single L1 "move"). They must take turns with the attacker because they need to target the attacker's last move in their response (these calls must be done in turns). However, while the attacker has no delay, the honest move is delayed every time by having to go through L1 and the long force inclusion delay.Because all progress is made by taking turns bisecting a 2^43 space of disagreements, it's easy to see that the number of turns is around 43 turn-based moves (as each bisection divides the space into two parts). In addition to this, the calls for
createLayerZeroEdge
for each level, and the final call toconfirmEdgeByOneStepProof
need to be included, for up to 50 turn-based actions.For example, for the smaller config used in the tests (3 intermediate levels and 6 bisections in each level), 31 turn-by-turn moves are made by each opponent.
Clearly, 50 moves (or even the test config's 31 moves), each delayed by up to 24 hours, very quickly causes the honest challengers to "time out" and exhaust their 7-day clock (from the time of the first move).
Here's a step-by-step scenario for the attack described in the submission:
newStakeOnNewAssertion
.createLayerZeroEdge
to challenge the claim.bisectEdge
to narrow down the disagreement space.confirmEdgeByTime
to finish the dispute, and callsconfirmAssertion
to prove its assertion.Tools Used
Manual review.
Recommended Mitigation Steps
The chess-clock and the force-inclusion delay are inherently incompatible with such a long turn-based game if played on L2. In its current highly-interactive form, the application of BOLD should be limited to deployments on L1 (for use in validation of L2s) only, and not for L3s.
Alternatively, two improvements can be made to make the attack less impactful:
createLayerZeroEdge
contains all 2^11 sub levels (how this can be possible is detailed below). This way there are only 2 "turns":createLayerZeroEdge
on first level (e.g., level 1) with 2048 sub-levels already detailed on-chain.createLayerZeroEdge
on second level, with all 2048 sub-levels included on-chain.The combination of these two factors reduce the number of turn-based moves the challenger (defender) needs to make to only 2. If each of those moves is delayed by 6 hours of the force inclusion delay, only 12 hours of the 7 days' clock is lost to the attack, allowing the dispute to proceed with manageable impact.
Posting the required sub-level data would be more expensive for each transaction, but it also reduces the overall amount of transactions and complexity. Additionally on L1, data blobs can be used for this need to make this cheaper. On L2 calldata will be used, which will be posted to L1 blobs, which also make the costs manageable.
In order to store this data on-chain, instead of storing the full data, a merkle root can be calculated and stored from the input data (on-chain). Although merkelization of 2048 would need to be executed on-chain it would be manageable (roughly 4096 hashes) costing around 200K gas. Any subsequent player would be able to construct a proof for any element into the tree from the previously posted data.
Assessed type
Other