Skip to content

GH-50314: [C++][Parquet] Reject invalid DELTA_BINARY_PACKED headers - #51128

Open
1fanwang wants to merge 2 commits into
apache:mainfrom
1fanwang:1fannnw/reject-invalid-delta-headers
Open

GH-50314: [C++][Parquet] Reject invalid DELTA_BINARY_PACKED headers#51128
1fanwang wants to merge 2 commits into
apache:mainfrom
1fanwang:1fannnw/reject-invalid-delta-headers

Conversation

@1fanwang

@1fanwang 1fanwang commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

A corrupt DELTA_BINARY_PACKED page can encode a miniblock count far larger than the page itself. The decoder allocated that buffer before discovering the input was incomplete. A single-value page never needs a miniblock buffer but still used the untrusted count.

What changes are included in this PR?

The decoder now:

  • leaves the miniblock buffer unallocated for single-value pages;
  • reserves the required min-delta byte before validating available bit-width bytes;
  • rejects impossible headers before allocation.

Are these changes tested?

cmake --build cpp/build-review --target parquet-encoding-test -j 8
cpp/build-review/debug/parquet-encoding-test \
  --gtest_filter='*SingleValueSkipsMiniblockAllocation*:*RejectsMiniblockWidthsLargerThanInput*:*RejectsMiniblockWidthsWithoutMinDelta*'
cpp/build-review/debug/parquet-encoding-test
Raw logs
# Regression tests with the decoder fix removed
Single-value pages allocated 1048576 bytes.
Missing-min-delta pages allocated 64 bytes and raised:
Unexpected end of stream: Decode bit-width EOF
[  FAILED  ] 4 tests.

# Current branch
[==========] Running 6 tests from 2 test suites.
[  PASSED  ] 6 tests.

# Full encoding suite
[==========] Running 141 tests.
[  PASSED  ] 141 tests.

Are there any user-facing changes?

Invalid pages fail before allocation with an error that names the impossible miniblock count and available bytes. Valid single-value pages decode without allocating a miniblock buffer.

GitHub Issue: #50314

…ders

InitHeader() sizes the bit-width buffer from the header's miniblock
count without tying it to the page size, so a 10-byte page claiming
2^20 miniblocks allocates 1 MiB before failing. InitBlock() reads one
bit-width byte per miniblock, so such a page can never decode.

Generated-by: GitHub Copilot CLI (Claude Opus 5)
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Copilot AI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

⚠️ GitHub issue #50314 has been automatically assigned in GitHub to PR creator.

Copilot AI 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.

🟡 Changes recommended

The decoder change still allocates miniblock scratch space unconditionally (including for single-value pages) and the new guard should account for required min_delta_ bytes, leaving a remaining allocation-DoS gap that should be closed.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR hardens the C++ Parquet DELTA_BINARY_PACKED decoder against corrupt page headers that can otherwise drive disproportionate memory allocations and produce misleading EOF errors, aligning behavior with the security/robustness goals described in GH-50314.

Changes:

  • Add header validation in DeltaBitPackDecoder::InitHeader to reject pages whose miniblock count is incompatible with the remaining input bytes and emit a more actionable ParquetException.
  • Add new encoding tests to cover the single-value page path and the corrupt-header rejection case (including allocation behavior via ProxyMemoryPool).
File summaries
File Description
cpp/src/parquet/decoder.cc Adds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.cc Adds regression tests for single-value decoding and for rejecting invalid miniblock-width headers without allocating.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cpp/src/parquet/decoder.cc Outdated
Comment on lines +1564 to +1568
// GH-50314: mini_blocks_per_block_ comes from the page header and sizes the
// allocation below, while InitBlock() reads one bit-width byte per miniblock.
// A count larger than the bytes left can never decode, so we reject it here
// instead of allowing it to drive a large allocation. A page holding a single
// value keeps that value in the header and never calls InitBlock(), so this

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 think Copilot is right that we should also handle total_value_count_ == 1 somehow @1fanwang . Perhaps in that case we should just skip the allocation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment thread cpp/src/parquet/encoding_test.cc Outdated
::testing::HasSubstr(
"the number of miniblocks per block (1048576) is larger than the "
"number of bytes remaining in the page (1)")));
EXPECT_EQ(pool.bytes_allocated(), 0);

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.

bytes_allocated is the number of bytes currently allocated. Do we want to use total_bytes_allocated instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 21133d6.

Single-value pages never initialize a block, so leave the bit-width buffer unallocated. Account for the required min-delta byte when validating block metadata, and use cumulative allocation counts in the regression tests.

Generated-by: GitHub Copilot CLI (Claude Opus 5)
Signed-off-by: 1fanwang <1fannnw@gmail.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

⚠️ GitHub issue #50314 has been automatically assigned in GitHub to PR creator.

Copilot AI review requested due to automatic review settings September 3, 2026 19:40

Copilot AI 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.

🟢 Approval recommended

The changes directly address the allocation-before-validation issue with clear guards and are covered by focused regression tests.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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