Skip to content

fix(bucket): reject oversized input in BucketUtils::HashBytes - #961

Open
WZhuo wants to merge 1 commit into
apache:mainfrom
WZhuo:fix_bucket_hash_oversized_length
Open

WZhuo wants to merge 1 commit into
apache:mainfrom
WZhuo:fix_bucket_hash_oversized_length

Conversation

@WZhuo

@WZhuo WZhuo commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

HashBytes passed a size_t length straight to the vendored MurmurHash3_x86_32, whose length parameter is a signed 32-bit int. For inputs of 2^31 bytes or more the length wrapped negative, making the hash loop read far out of bounds (and nblocks * 4 signed-overflow UB); for inputs above 2^32 bytes a silently wrong prefix was hashed, breaking the bucket contract against other implementations.

Every literal hash path (kDecimal/kString/kUuid/kBinary/kFixed) funnels through this one function, so guard it there: reject lengths above INT32_MAX with ICEBERG_CHECK_OR_DIE before the narrowing cast. The bound matches the limit implied by iceberg-java's byte arrays, so no legal bucket input is rejected. The vendored murmurhash3_internal.cc is left untouched (tagged third-party code) and the now-checked narrowing is made explicit with static_cast.

Add BucketUtilsTest.HashBytesRejectsOversizedInput, which fabricates an oversized span over a 1-byte buffer (never dereferenced, so no 2 GiB allocation is needed in CI). Without the guard the test segfaults.

HashBytes passed a size_t length straight to the vendored
MurmurHash3_x86_32, whose length parameter is a signed 32-bit int. For
inputs of 2^31 bytes or more the length wrapped negative, making the
hash loop read far out of bounds (and nblocks * 4 signed-overflow UB);
for inputs above 2^32 bytes a silently wrong prefix was hashed, breaking
the bucket contract against other implementations.

Every literal hash path (kDecimal/kString/kUuid/kBinary/kFixed) funnels
through this one function, so guard it there: reject lengths above
INT32_MAX with ICEBERG_CHECK_OR_DIE before the narrowing cast. The bound
matches the limit implied by iceberg-java's byte arrays, so no legal
bucket input is rejected. The vendored murmurhash3_internal.cc is left
untouched (tagged third-party code) and the now-checked narrowing is
made explicit with static_cast<int>.

Add BucketUtilsTest.HashBytesRejectsOversizedInput, which fabricates an
oversized span over a 1-byte buffer (never dereferenced, so no 2 GiB
allocation is needed in CI). Without the guard the test segfaults.
Copilot AI lite review requested due to automatic review settings September 24, 2026 09:45

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

Copilot review overview

🟡 Changes recommended

The new regression test constructs a std::span with a count far beyond the referenced object size, which is undefined behavior even if the span is never dereferenced.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

This PR hardens BucketUtils::HashBytes by rejecting oversized byte inputs before calling the vendored MurmurHash3_x86_32 function, preventing signed-length wraparound that could otherwise lead to out-of-bounds reads and incorrect bucket hashes for very large inputs.

Changes:

  • Add a size guard in BucketUtils::HashBytes to reject inputs larger than INT32_MAX before narrowing to the int length parameter required by MurmurHash3_x86_32.
  • Make the narrowing cast explicit (static_cast<int>(bytes.size())) after the guard.
  • Add a regression test intended to ensure oversized inputs are rejected without requiring a multi-GB allocation.
File Description
src/​iceberg/​util/​bucket_util.cc Adds an explicit maximum-length guard before calling MurmurHash3 with an int length.
src/​iceberg/​test/​bucket_util_test.cc Adds a regression test for rejecting inputs larger than INT32_MAX.

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

Comment on lines +120 to +124
const uint8_t byte = 0;
const auto oversized_length =
static_cast<size_t>(std::numeric_limits<int32_t>::max()) + 1;
std::span<const uint8_t> oversized(&byte, oversized_length);
ASSERT_THAT([&]() { BucketUtils::HashBytes(oversized); },

This branch has not been deployed

No deployments
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.

2 participants