Skip to content

parquet: bounded skip chunk in DeltaBitPackDecoder::skip - #2

Merged
Vedin merged 1 commit into
embucket-parquet-59.2.0from
embucket-parquet-59.2.0-bounded-skip
Sep 12, 2026
Merged

Vedin merged 1 commit into
embucket-parquet-59.2.0from
embucket-parquet-59.2.0-bounded-skip

Conversation

@Vedin

@Vedin Vedin commented Sep 12, 2026

Copy link
Copy Markdown

What

DeltaBitPackDecoder::skip no longer sizes its buffer from values_per_mini_block (an unbounded ULEB128 page-header field, only divisibility-checked): a malformed file could force an arbitrarily large allocation and abort the process.

It now decodes each mini-block through a fixed SKIP_CHUNK = 64 stack array in min(remaining, 64) pieces, updating last_value exactly as before; the bit_width == 0 fast path is untouched. Any mini-block size accepted by set_data (Snowflake-written files use sizes other than 32/64) is handled with zero heap allocation; for 32/64 the behaviour is bit-identical.

Tests

  • Test-only DELTA_BINARY_PACKED encoder with configurable block_size / mini_blocks_per_block (sanity-checked against the built-in 128/4 layout): 256/2, 512/1, 256/1 — full decode, skip(n) + tail get for n = 1, 5, 100, 129, 300, block/mini-block boundaries ±1, past the end, consecutive partial skips.
  • Oversized header (block_size = 2^40, total_count = 1000, one payload byte): skip(10) and get return Err promptly. With the previous code this test aborts with an 8 TiB allocation failure.
  • cargo test -p parquet --lib encodings::decoding: 85 passed; fmt and clippy clean.

Pinned by Embucket/rustice ([patch.crates-io], whole arrow 59.2.0 workspace at this rev).

🤖 Generated with Claude Code

https://claude.ai/code/session_012m5Yx6yEpZsacqAhZotgkT

…page header

3007f88 sized the skip buffer from `values_per_mini_block` so that mini-block sizes
other than 32/64 (as written by Snowflake) could be skipped. That value is derived from
the page header (`block_size / mini_blocks_per_block`, both unbounded ULEB128, only
checked for divisibility), so a malformed file could make `skip` allocate an arbitrarily
large Vec and abort the process (`memory allocation of 8796093022208 bytes failed`).

Replace the header-sized Vec with a fixed 64-value stack array (`SKIP_CHUNK`) and decode
each `bit_width > 0` mini-block through it in chunks, updating `last_value` exactly as
before. The bit reader is positional, so chunking consumes the same bits; the
`bit_width == 0` fast path is unchanged. Any mini-block size that `set_data` accepts is
now handled with no allocation at all.

Tests (parquet/src/encodings/decoding.rs):
- a test-only DELTA_BINARY_PACKED encoder with configurable block_size /
  mini_blocks_per_block (the built-in encoder always writes 128/4), sanity-checked
  against the built-in layout;
- Snowflake-like layouts 256/2 (128 per mini-block), 512/1 and 256/1 (i32): full decode
  in uneven `get` calls, `skip(n)` + tail `get` for n inside a mini-block, across
  mini-block and block boundaries, larger than a mini-block/block, and past the end, plus
  consecutive partial skips;
- an oversized header (block_size = 2^40, one mini-block, total_count = 1000, one payload
  byte): `set_data` accepts it, `skip(10)` and `get` return `Err` promptly instead of
  allocating. With the previous code this test aborts with an 8 TiB allocation failure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012m5Yx6yEpZsacqAhZotgkT
@Vedin
Vedin merged commit 923cf78 into embucket-parquet-59.2.0 Sep 12, 2026
11 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant