Skip to content

fix(derivation): reject invalid blockCount to prevent batch-parse DoS#995

Merged
tomatoishealthy merged 1 commit into
feat/sequencer-finalfrom
fix/derivation-blockcount-panic
Jun 16, 2026
Merged

fix(derivation): reject invalid blockCount to prevent batch-parse DoS#995
tomatoishealthy merged 1 commit into
feat/sequencer-finalfrom
fix/derivation-blockcount-panic

Conversation

@curryxbo

@curryxbo curryxbo commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What

Fixes two DoS panics in node/derivation/batch_info.go::ParseBatch that let a malformed L1 commitBatch crash any node in layer1 verify mode (the derivation goroutine has no recover()). Both originate from an unvalidated blockCount, where LastBlockNumber is attacker-controlled L1 calldata.

  • blockCount == 0 (lastBlockNumber == parent): the underflow guard rejected < but not ==, producing empty blockContexts; derive() then returned a nil header that derivation.go dereferenced → nil-pointer panic. Zero-block batches are not supported, so this is rejected at parse.
  • overflowing blockCount: blockCount * 60 wrapped uint64 to a small value, bypassing the length guard, then make([]*BlockContext, int(blockCount)) attempted an out-of-range allocation → makeslice panic / OOM.

Fix (targeted, one file)

In ParseBatch:

  1. Bound blockCount by payload length via division (blockCount > len(batchBytes)/60) before the multiply — overflow-safe, and make is bounded by real payload size.
  2. Reject blockCount == 0 once after both branches — covers every path (parent-equal, zero prefix, v0-nil).

Both become clean parse errors instead of process-killing panics. derivation.go is unchanged.

Note: single-block batches are unaffected

The guard rejects only blockCount == 0. A single-block batch is blockCount == 1 and passes untouched — including the fork-boundary case (e.g. the zk→MPT / Morph203 transition block committed in its own batch). The V0→V1 transition path independently computes lastBlockNumber - startBlock + 1, so it is structurally ≥ 1 and can never be zero.

Test

TestParseBatchBlockCountBounds: zero count → ParseBatch returns an error; overflowing count → error (no panic). Existing ParseBatch tests and the full derivation package test suite pass.

Follow-up (out of scope)

Root cause is contract-side: Rollup.sol::_commitBatch does not constrain lastBlockNumber vs. parent and _getBLSMsgHash is still a return bytes32(0) stub, so the field isn't signature-bound. A span cap (and tighter signing) is recommended separately (see #994).

Closes #994

@curryxbo curryxbo requested a review from a team as a code owner June 16, 2026 03:24
@curryxbo curryxbo requested review from r3aker86 and removed request for a team June 16, 2026 03:24
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4cfdc842-54a8-46d9-b0be-daa0ce1e8e58

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/derivation-blockcount-panic

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

A malformed L1 commitBatch could crash layer1-verify nodes (no recover() in
the derivation goroutine) via two panics rooted in an unvalidated blockCount
in ParseBatch:

- blockCount == 0 (lastBlockNumber == parent) produced empty blockContexts;
  derive() then returned a nil header that derivation.go dereferenced.
  Zero-block batches are not supported, so reject them at parse.
- a huge blockCount let blockCount*60 overflow uint64 and bypass the length
  guard, driving an out-of-range make([]*BlockContext). Bound blockCount by
  payload length using division (overflow-safe) before the multiply.

Both become clean parse errors instead of process-killing panics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tomatoishealthy tomatoishealthy merged commit ebf7582 into feat/sequencer-final Jun 16, 2026
7 checks passed
@tomatoishealthy tomatoishealthy deleted the fix/derivation-blockcount-panic branch June 16, 2026 09:15
curryxbo pushed a commit that referenced this pull request Jun 16, 2026
_commitBatch accepted an unconstrained lastBlockNumber, so a single active
staker could commit a batch with lastBlockNumber <= parent.lastBlockNumber
(blockCount == 0). The field is not signature-bound (the _getBLSMsgHash BLS
path is still a bytes32(0) stub), so this needs no collusion. Derivation
nodes in layer1 verify mode then panic (pre-#995) or stall permanently
(post-#995) on the malformed batch — a network-wide DoS whose root cause is
on-chain.

Enforce a non-empty, monotonic block span in _commitBatchWithBatchData
(shared by commitBatch / commitState / commitBatchWithProof): when the
parent header carries a lastBlockNumber (V1+), require
lastBlockNumber > parentLastBlockNumber. This mirrors the node's
blockCount = lastBlockNumber - parentLastBlockNumber derivation. The V0->V1
transition (parent version 0) carries no lastBlockNumber and is a historical
one-time event, so it is not guarded here.

Tests: empty span (== parent, the off-by-one the original underflow guard
missed) reverts with "empty block span"; a strictly increasing span still
commits (the only happy-path coverage for V1 parents — the existing suite
is all V0). Full Rollup suite passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

derivation: malformed L1 commitBatch panics layer1-verify nodes (blockCount==0 nil deref + blockCount*60 overflow)

2 participants