Repository navigation
Conversation
Track logical bit lengths separately from byte lengths when shifting sliced bitmaps, stop before the first byte outside the declared slice, and clear unused output bits. Add an exact-sized-buffer regression test that checks both serialized output and round-trip behavior. Constraint: Preserve the streaming writer and Feather V1 byte layout. Rejected: Copy the entire bitmap into a temporary allocation | adds allocation and abandons the existing bounded streaming design. Confidence: high Scope-risk: narrow Directive: Keep bitmap length in bits; byte length alone cannot determine whether the final shifted source byte is valid. Tested: ASan/UBSan standalone control and trigger; full arrow-feather-test suite (84 passed, 5 expected skips). Not-tested: Full Arrow repository test suite.
|
|
| for (int64_t i = 0; i < length; ++i) { | ||
| uint8_t r = static_cast<uint8_t>(*data++ >> bit_shift); | ||
| uint8_t l = static_cast<uint8_t>(*data << lshift); | ||
| uint8_t l = | ||
| data == data_end ? 0 : static_cast<uint8_t>(*data << lshift); | ||
| uint8_t value = l | r; | ||
| if (i == length - 1 && bit_length % 8 != 0) { | ||
| value &= bit_util::LeastSignificantBitMask<uint8_t>(bit_length % 8); | ||
| } | ||
| *buffer_it++ = value; | ||
| if (buffer_it == buffer_end) { | ||
| RETURN_NOT_OK(stream->Write(buffer, buffersize)); | ||
| buffer_it = buffer; | ||
| } | ||
| } | ||
| if (buffer_it != buffer) { | ||
| RETURN_NOT_OK(stream->Write(buffer, buffer_it - buffer)); | ||
| } |
There was a problem hiding this comment.
Would it be easier to let the loop run only until i + 1 < length.
And then change L109-111:
uint8_t r = static_cast<uint8_t>(*data++ >> bit_shift);
*buffer_it++ = r;
RETURN_NOT_OK(stream->Write(buffer, buffer_it - buffer));
I.e. always handle the last byte after the loop.
There was a problem hiding this comment.
Ah, it might still need masking of the upper bits of r.
| return WriteBuffer(buffer, | ||
| bit_util::BytesForBits(values.length() * fw_type.bit_width()), | ||
| bit_offset); | ||
| return WriteBuffer( |
There was a problem hiding this comment.
Let's add a DCHECK_EQ(fw_type.bit_width() % 8, 0) to make sure we're not handling any non-byte-aligned type here?
| return WriteBitmap(prim_values.values()->data(), values.length(), | ||
| prim_values.offset()); | ||
| } | ||
| const uint8_t* buffer = |
There was a problem hiding this comment.
Since the above is if constexpr, we might want to use an else branch here (making it constexpr too).
| if (i == length - 1 && bit_length % 8 != 0) { | ||
| value &= bit_util::LeastSignificantBitMask<uint8_t>(bit_length % 8); | ||
| } |
There was a problem hiding this comment.
Is this actually required? I don't think it matters that we are writing potentially non-zero bits past the logical end of the bitmap.
| ASSERT_GT(output_->size(), 8); | ||
| ASSERT_EQ(output_->data()[8], 0x01); |
There was a problem hiding this comment.
Is this testing the raw Feather bytes? Can you add a comment explaining these checks?
There was a problem hiding this comment.
And by the way, if output_'s size is 8, then you cannot read beyond output_->data()[7].
| auto data = | ||
| ArrayData::Make(boolean(), 1, {nullptr, values}, /*null_count=*/0, /*offset=*/1); |
There was a problem hiding this comment.
Can we have a loop running offset from 0 to 7?
| } | ||
|
|
||
| TEST_P(TestFeather, ExactSizeSlicedBooleanBuffer) { | ||
| if (GetParam().version != kFeatherV1Version) { |
There was a problem hiding this comment.
Add a comment at top pointing to the GH issue?
Rationale for this change
The Feather V1 bitmap shift loop unconditionally read the byte after the final
source byte. For an exact-sized sliced bitmap, this caused a one-byte
out-of-bounds read and copied data outside the logical slice into unused bits of
the serialized bitmap.
Closes #50762.
What changes are included in this PR?
bitmaps.
byte and round-trip result.
Are these changes tested?
Yes.
arrow-feather-testsuite under ASan/UBSan: 84 passed, 5 expectedskips.
control and offset-one trigger both complete cleanly after the fix.
Are there any user-facing changes?
No public API changes. Feather V1 output no longer incorporates bits outside a
logical bitmap slice.
This PR contains a Critical Fix. It prevents an out-of-bounds read and
incorrect data from being copied into serialized output when the documented
array-buffer contract is upheld.
AI assistance
I used an AI coding assistant to help trace the writer, draft the implementation
and regression test, and run the verification commands. I reviewed the complete
diff, reproduced the original fault under ASan, verified the bit-boundary
logic, and ran the full Feather test suite. No maintainer was tagged or pinged.