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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

@1fanwang1fanwang 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>
CopilotAI lite review requested due to automatic review settings September 1, 2026 22:32
@github-actions

Copy link
Copy Markdown

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

CopilotAI 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
FileDescription
cpp/src/parquet/decoder.ccAdds early validation/error reporting for invalid DELTA_BINARY_PACKED miniblock headers (and aims to prevent oversized allocations).
cpp/src/parquet/encoding_test.ccAdds 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 threadcpp/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
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 21133d6.

@github-actionsgithub-actionsBot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 2, 2026
Comment threadcpp/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
ContributorAuthor

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

Copy link
Copy Markdown

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

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

CopilotAI 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 freeto 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

@1fanwang@pitrou