Skip to content

Bound packed channel reference copies to the channel's byte range - #809

Open
iliasabk wants to merge 1 commit into
boostorg:developfrom
iliasabk:fix/packed-channel-ref-byte-bound
Open

iliasabk wants to merge 1 commit into
boostorg:developfrom
iliasabk:fix/packed-channel-ref-byte-bound

Conversation

@iliasabk

Copy link
Copy Markdown

Issue

Fixes #808 — get_data()/set_data() in packed_channel_reference_base copied sizeof(BitField) bytes unconditionally. For bit-aligned images whose last pixel occupies fewer bits than a full BitField (e.g. bit_aligned_image3_type<1,2,1> = 4 bits/px on a 16-bit field), accessing the final pixel read or wrote past the end of the image buffer. Reproduced locally as a heap-buffer-overflow under ASan on the issue's MWE, and it is not limited to tiny images: any bit-aligned image whose total bit count is not a multiple of the BitField width truncates its last field (an 8×2 rgb121 image overruns its 8-byte buffer the same way).

Root cause

A channel reference only ever needs the bytes that contain its bit range [first_bit, first_bit + NumBits) relative to data_ptr, but both the static (packed_channel_reference) and dynamic (packed_dynamic_channel_reference) paths always copied the full BitField. The copy is byte-wise (static_copy_bytes), so the overrun hits both reads (get) and writes (set_unsafe, set_from_reference).

Fix

Add bounded get_data(byte_count)/set_data(val, byte_count) overloads to packed_channel_reference_base and call them with ceil((first_bit + NumBits) / 8) — compile-time for packed_channel_reference, runtime for the dynamic references. The clamp to sizeof(bitfield_t) keeps degenerate offsets in bounds. Because the copy is byte-address-ordered into/out of the bitfield object, copying the same leading byte range preserves bit positions identically to the full copy on any endianness; read-modify-write semantics are unchanged since set_unsafe writes back exactly the byte range it read. The opt-in BOOST_GIL_CONFIG_HAS_UNALIGNED_ACCESS path is left untouched — the bounded overloads are defined outside the #if and used by all four reference classes regardless.

Verification (local, ASan, Boost 1.92)

  • Issue MWE (bit_aligned_image3_type<1,2,1>, 2×1): crashes before (heap-buffer-overflow in static_copy_bytes via packed_dynamic_channel_reference::set_unsafe), runs clean after; written channel values read back correctly.
  • 8×2 rgb121 write/read sweep of all 32 channels: correct values, no overflow; byte-level read-modify-write preserves untouched bits.
  • Byte-crossing channels (bit_aligned_image3_type<3,5,3> on a 32-bit field, 3×1): crashes before, all 9 channel values correct after.
  • packed_channel_reference<uint16_t,3,5> static path incl. reference-to-reference assignment (set_from_reference): 0xBEEF/17 → 0xBE8F, 0xBEEF,0x1234 → 0xBE37, exactly as expected.

clang++ -std=c++17 -fsanitize=address on arm64 macOS, no other files touched.

get_data()/set_data() copied sizeof(BitField) bytes unconditionally.
For bit-aligned images whose last pixel occupies fewer bits than a
full BitField (e.g. bit_aligned_image3_type<1,2,1> = 4 bits/px on a
16-bit field), accessing the final pixel read or wrote past the end
of the image buffer (heap-buffer-overflow, boostorg#808).

Copy only ceil((first_bit + NumBits) / 8) bytes -- the bytes that
actually contain the referenced channel's bits. Untouched bits keep
their storage contents since the read-modify-write in set_unsafe()
writes back exactly the byte range it read.
@sdebionne

Copy link
Copy Markdown
Contributor

Hi, thanks for running your AI agent on this issue. Could you please tell me what motivated you to create this PR and eventually which of your project uses GIL?

@iliasabk

Copy link
Copy Markdown
Author

Hi @sdebionne, happy to clarify. No project of mine uses GIL directly — I audit memory safety in open-source image and parsing libraries, and your report in #808 was a clean, well-scoped bug with an MWE, so I reproduced it under ASan and sent a bounded-copy fix upstream. I hope it helps; happy to reshape the approach or add a regression test if you'd prefer a different structure.


auto operator=(mutable_reference const& ref) const -> packed_channel_reference const& { set_from_reference(ref.get_data()); return *this; }
auto operator=(const_reference const& ref) const -> packed_channel_reference const& { set_from_reference(ref.get_data()); return *this; }
auto operator=(mutable_reference const& ref) const -> packed_channel_reference const& { set_from_reference(ref.get_data((FirstBit + NumBits + 7) / 8)); return *this; }

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.

Could you factorize the (FirstBit + NumBits + 7) / 8) as a static constexpr and give it a meaningful name ?

@sdebionne sdebionne added this to the Boost 1.93 milestone Oct 1, 2026

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.

Heap buffer overflow with bit_aligned images

3 participants