Skip to content

fix: bound proof span in VerifySubtreeRootInclusion before allocating ranges#325

Open
evan-forbes wants to merge 1 commit into
mainfrom
fix/subtree-root-inclusion-alloc-bound
Open

fix: bound proof span in VerifySubtreeRootInclusion before allocating ranges#325
evan-forbes wants to merge 1 commit into
mainfrom
fix/subtree-root-inclusion-alloc-bound

Conversation

@evan-forbes

Copy link
Copy Markdown
Member

VerifySubtreeRootInclusion calls ToLeafRanges (allocating O((End-Start)/subtreeWidth) ranges from an attacker-controlled proof.End()) before the cheap len(subtreeRoots) count check; this fix rejects proofs whose span exceeds len(subtreeRoots)*subtreeWidth before allocating.

This is strictly hardening with very limited production accessibility: the only in-tree caller (celestia-node's CommitmentProof.Verify) is not network-reachable and its width derivation already curbs allocation; the exposure is external consumers that verify untrusted proofs with a fixed small width, so fixing because it is cheap and correct.

🤖 Generated with Claude Code

VerifySubtreeRootInclusion called ToLeafRanges (allocating one LeafRange
per ~subtreeWidth leaves of the attacker-controlled
[proof.Start(), proof.End()) span) before the cheap len(subtreeRoots)
count check. A small fixed subtreeWidth and a large proof.End() caused
allocation to grow linearly with the attacker-chosen span.

Reject up front any proof whose span exceeds len(subtreeRoots)*subtreeWidth
leaves, using an overflow-safe ceil(span/subtreeWidth) comparison, so the
expensive range construction never runs for such proofs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@evan-forbes evan-forbes requested a review from a team as a code owner May 22, 2026 21:02
@evan-forbes evan-forbes requested review from rach-id and removed request for a team May 22, 2026 21:02

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a security enhancement to the VerifySubtreeRootInclusion function by validating the proof span before performing potentially expensive allocations. This change prevents memory and CPU exhaustion attacks where an attacker could provide a large, controlled End value to trigger excessive memory allocation in ToLeafRanges. Additionally, a new test case, TestVerifySubtreeRootInclusion_allocationBound, has been added to verify that the short-circuit logic correctly rejects invalid spans before allocation occurs. I have no further feedback to provide.

Comment thread proof.go
Comment on lines +553 to +579
// Bound the proof span before constructing the leaf ranges. ToLeafRanges
// allocates one LeafRange per (at most) subtreeWidth leaves of the span
// [proof.Start(), proof.End()), and proof.End() is attacker-controlled.
// Each provided subtree root can cover at most subtreeWidth leaves, so a
// proof whose span exceeds len(subtreeRoots)*subtreeWidth leaves can never
// satisfy the len(subtreeRoots) == len(ranges) check below. Reject such
// proofs here, before allocating, to avoid memory/CPU amplification from a
// large attacker-chosen span.
if subtreeWidth <= 0 {
return false, fmt.Errorf("subtree root width %d should be strictly positive", subtreeWidth)
}
if len(subtreeRoots) == 0 {
return false, fmt.Errorf("number of subtree roots cannot be zero")
}
// span is guaranteed >= 1 because proof.Start() < proof.End() was checked above.
span := proof.End() - proof.Start()
// Each leaf range covers at most subtreeWidth leaves, so ToLeafRanges will
// produce at least ceil(span/subtreeWidth) ranges. If that already exceeds
// len(subtreeRoots) the count check below would fail, so reject now. The
// comparison is written with division to avoid overflowing the int
// multiplication len(subtreeRoots)*subtreeWidth on attacker-chosen spans:
// ceil(span/subtreeWidth) > len(subtreeRoots).
quotient := span / subtreeWidth
remainder := span % subtreeWidth
if quotient > len(subtreeRoots) || (quotient == len(subtreeRoots) && remainder != 0) {
return false, fmt.Errorf("proof span %d exceeds the maximum number of leaves that %d subtree roots of width %d can cover", span, len(subtreeRoots), subtreeWidth)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the comments could be summarised to one-two lines maximum. This is too much documentation in the code

@rootulp rootulp self-assigned this Jun 12, 2026
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.

3 participants