Skip to content

fix(chain): cap block::Hash and CountedHeader preallocation at protocol-level constants#10570

Merged
mergify[bot] merged 1 commit into
ZcashFoundation:mainfrom
dingledropper:fix/block-hash-cap-and-counted-header
May 25, 2026
Merged

fix(chain): cap block::Hash and CountedHeader preallocation at protocol-level constants#10570
mergify[bot] merged 1 commit into
ZcashFoundation:mainfrom
dingledropper:fix/block-hash-cap-and-counted-header

Conversation

@dingledropper

Copy link
Copy Markdown
Contributor

Motivation

block::Hash::max_allocation previously returned 65,535 (derived from MAX_PROTOCOL_MESSAGE_LEN / 32), and CountedHeader::max_allocation returned ~1,409 (same message-size-derived shape). Both are reachable via Vec::zcash_deserialize from any post-handshake peer through getblocks, getheaders, and headers messages. A peer can claim up to those counts and force Vec::with_capacity preallocation before any payload bytes are read.

The defense-in-depth fix at the deserializer level lands in PR #10563 (Vec::with_capacity(external_count.min(1024))); this PR caps the per-type allocation to match the actual protocol-level ceilings, mirroring Bitcoin Core's MAX_LOCATOR_SZ for locators and reusing Zebra's existing MAX_HEADERS_PER_MESSAGE for headers.

CWE-770 (Allocation of Resources Without Limits or Throttling).

Solution

Cap at protocol-level constants:

  1. Locator (block::Hash): introduce MAX_BLOCK_LOCATOR_LENGTH = 101 in zebra-chain/src/block.rs, matching Bitcoin Core's MAX_LOCATOR_SZ in net_processing.cpp (which zcashd inherits). block::Hash::max_allocation() now returns this constant.

  2. Headers (CountedHeader): reuse the existing MAX_HEADERS_PER_MESSAGE = 160 constant from zebra-chain/src/serialization/zcash_serialize.rs, already enforced on the sending side and at the codec level for read_headers (PR fix(network): Enforce 160-entry cap in read_headers #10528). CountedHeader::max_allocation() now returns MAX_HEADERS_PER_MESSAGE as u64. No new constant introduced — single source of truth across send/receive paths.

  3. Test cleanup: BLOCK_HEADER_LENGTH and MIN_COUNTED_HEADER_LEN (used only by the old message-size-derived max_allocation formula in header.rs) move into the preallocate test module where they are the only consumers. Avoids dead_code warnings under --features proptest-impl with -D warnings.

Test evidence

  • cargo fmt --all -- --check
  • cargo test -p zebra-chain --lib block::tests::preallocate ✓ (4 tests)
  • cargo clippy -p zebra-chain --all-targets -- -D warnings
  • cargo clippy -p zebra-chain --all-targets --features proptest-impl -- -D warnings

The block_hash_max_allocation and counted_header_max_allocation proptests are updated to assert the new constant-based caps:

  • Hash::max_allocation() == MAX_BLOCK_LOCATOR_LENGTH
  • CountedHeader::max_allocation() == MAX_HEADERS_PER_MESSAGE as u64

Context

CHANGELOG

Updated CHANGELOG.md under [Unreleased] → Security with the standard form referencing the protocol-level constants and the GHSA-xr93-pcq3-pxf8 sibling.

AI disclosure

Patch and PR description drafted with assistance from Claude (Anthropic), following @mpguerra's email-level scope ("MAX_BLOCK_LOCATOR_LENGTH = 101 matching Bitcoin Core's MAX_LOCATOR_SZ" and "CountedHeader cap as defense-in-depth"). I reviewed each step and am the responsible author.

@claude claude 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

…ol-level constants

`block::Hash::max_allocation` previously returned 65,535 (derived from
`MAX_PROTOCOL_MESSAGE_LEN / 32`), and `CountedHeader::max_allocation`
returned ~1,409 (same message-size-derived shape). Both are reachable via
`Vec::zcash_deserialize` from any post-handshake peer through `getblocks`,
`getheaders`, and `headers` messages. A peer can claim up to those counts
and force `Vec::with_capacity` preallocation before any payload bytes are
read.

Cap at protocol-level constants:
- `MAX_BLOCK_LOCATOR_LENGTH = 101`, a new `block`-module constant that
  matches Bitcoin Core's `MAX_LOCATOR_SZ` in `net_processing.cpp` (which
  zcashd inherits).
- The existing `MAX_HEADERS_PER_MESSAGE = 160` constant from
  `crate::serialization::zcash_serialize`, already enforced on the sending
  side and at the codec level for `read_headers` (PR ZcashFoundation#10528).

Same fix shape applied to `AddrV1`/`AddrV2` in PR ZcashFoundation#10494 for
GHSA-xr93-pcq3-pxf8.

CWE-770 (Allocation of Resources Without Limits or Throttling).

Tests:
- `block_hash_max_allocation` proptest updated to assert
  `Hash::max_allocation() == MAX_BLOCK_LOCATOR_LENGTH`.
- `counted_header_max_allocation` proptest updated to assert
  `CountedHeader::max_allocation() == MAX_HEADERS_PER_MESSAGE as u64`.
- Test-only constants `BLOCK_HEADER_LENGTH` and `MIN_COUNTED_HEADER_LEN`,
  which were used by the old message-size-derived formula in `header.rs`,
  moved into the `preallocate` test module where they are the only
  consumers.
@natalieesk

Copy link
Copy Markdown
Contributor

Thank you for your PR. We will take a look shortly.

@oxarbitrage
oxarbitrage force-pushed the fix/block-hash-cap-and-counted-header branch from 5bbdec7 to 59a2f9c Compare May 25, 2026 11:12

@oxarbitrage oxarbitrage left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff. The fix is correct and well-scoped:

  • Hash::max_allocation: 65,535 to 101 (matches Bitcoin Core MAX_LOCATOR_SZ; legitimate locators are ~32 entries)
  • CountedHeader::max_allocation: 1,409 to 160 (reuses existing MAX_HEADERS_PER_MESSAGE already enforced in the codec)
  • Stacks correctly with #10563 generic 1024-element deserializer cap
  • Both deserialization paths are live (read_getblocks, read_getheaders, read_headers)
  • Tests updated correctly, unused constants moved to test module

Rebased onto main, resolved CHANGELOG conflict. All local checks pass (cargo test, clippy, fmt).

@mergify mergify Bot added the queued label May 25, 2026
@mergify

mergify Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

This pull request spent 11 minutes 55 seconds in the queue, including 35 seconds running CI.

Required conditions to merge

@oxarbitrage
oxarbitrage requested a review from mpguerra May 25, 2026 11:49
mergify Bot added a commit that referenced this pull request May 25, 2026
@mergify
mergify Bot merged commit 8981a1b into ZcashFoundation:main May 25, 2026
132 of 138 checks passed
@mergify mergify Bot removed the queued label May 25, 2026
judah-caruso pushed a commit to ShieldedLabs/zebra-crosslink-staging that referenced this pull request May 28, 2026
…ol-level constants (ZcashFoundation#10570)

`block::Hash::max_allocation` previously returned 65,535 (derived from
`MAX_PROTOCOL_MESSAGE_LEN / 32`), and `CountedHeader::max_allocation`
returned ~1,409 (same message-size-derived shape). Both are reachable via
`Vec::zcash_deserialize` from any post-handshake peer through `getblocks`,
`getheaders`, and `headers` messages. A peer can claim up to those counts
and force `Vec::with_capacity` preallocation before any payload bytes are
read.

Cap at protocol-level constants:
- `MAX_BLOCK_LOCATOR_LENGTH = 101`, a new `block`-module constant that
  matches Bitcoin Core's `MAX_LOCATOR_SZ` in `net_processing.cpp` (which
  zcashd inherits).
- The existing `MAX_HEADERS_PER_MESSAGE = 160` constant from
  `crate::serialization::zcash_serialize`, already enforced on the sending
  side and at the codec level for `read_headers` (PR ZcashFoundation#10528).

Same fix shape applied to `AddrV1`/`AddrV2` in PR ZcashFoundation#10494 for
GHSA-xr93-pcq3-pxf8.

CWE-770 (Allocation of Resources Without Limits or Throttling).

Tests:
- `block_hash_max_allocation` proptest updated to assert
  `Hash::max_allocation() == MAX_BLOCK_LOCATOR_LENGTH`.
- `counted_header_max_allocation` proptest updated to assert
  `CountedHeader::max_allocation() == MAX_HEADERS_PER_MESSAGE as u64`.
- Test-only constants `BLOCK_HEADER_LENGTH` and `MIN_COUNTED_HEADER_LEN`,
  which were used by the old message-size-derived formula in `header.rs`,
  moved into the `preallocate` test module where they are the only
  consumers.

Co-authored-by: dingledropper <dingledropper@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants