Skip to content

fix(jit_allocator): bound BitVectorRangeIterator range_start to _end#3

Merged
bluestreak01 merged 3 commits into
masterfrom
fix/jit-allocator-search-end-bounds
May 27, 2026
Merged

fix(jit_allocator): bound BitVectorRangeIterator range_start to _end#3
bluestreak01 merged 3 commits into
masterfrom
fix/jit-allocator-search-end-bounds

Conversation

@puzpuzpuz
Copy link
Copy Markdown

@puzpuzpuz puzpuzpuz commented May 27, 2026

BitVectorRangeIterator::next_range() did not clamp *range_start against _end. The iterator's init() only masks bits before start, so the underlying BitWord can carry free bits past end. When ctz picked such a bit, the caller would compute range_size = range_end - range_start as size_t (range_end gets clamped to _end), the subtraction would underflow to a huge value, and a range that lies entirely past the search region would be accepted.

Inside JitAllocator::alloc this produced an area_index outside the block: the returned Span reported its requested size even though the underlying memory ran past the block boundary into unmapped pages. QuestDB caught this as a SIGSEGV inside JitRuntime::_add's memcpy: a production core dump shows a block with _area_size=4096, _search_end=4083 and 3 free bits at the tail (areas 4093-4095). A 7-area request let the iterator pick range_start=4093 with range_end clamped to 4083, _area_used over-incremented by 4, the Span claimed 448 bytes when only 192 were valid, and the JIT runtime memcpy walked off the end of the block.

The fix adds a single bound check: if *range_start >= _end after the ctz, the iterator reports end-of-iteration. Two regression tests cover the path:

  • test_bit_vector_range_iterator_bounds() exercises the iterator directly with two hand-crafted bitmaps (same-word and multi-word search regions). Without the fix EXPECT_LT(range_start, end) fails on the first iterator call.

  • test_jit_allocator_search_end_bounds() drives JitAllocator into the production state (area_used = area_size - 14, free bits both inside the search range and at the tail past _search_end). With the bug the alloc trips the existing offset <= block_size - size assertion at line ~999; with the fix the search reports no fit and a fresh block is allocated.

BitVectorRangeIterator::next_range() did not clamp *range_start against
_end. The iterator's init() only masks bits before start, so the
underlying BitWord can carry free bits past end. When ctz picked such a
bit, the caller would compute range_size = range_end - range_start as
size_t (range_end gets clamped to _end), the subtraction would underflow
to a huge value, and a range that lies entirely past the search region
would be accepted.

Inside JitAllocator::alloc this produced an area_index outside the
block: the returned Span reported its requested size even though the
underlying memory ran past the block boundary into unmapped pages.
QuestDB caught this as a SIGSEGV inside JitRuntime::_add's memcpy: a
production core dump shows a block with _area_size=4096,
_search_end=4083 and 3 free bits at the tail (areas 4093-4095). A
7-area request let the iterator pick range_start=4093 with range_end
clamped to 4083, _area_used over-incremented by 4, the Span claimed 448
bytes when only 192 were valid, and the JIT runtime memcpy walked off
the end of the block.

The fix adds a single bound check: if *range_start >= _end after the
ctz, the iterator reports end-of-iteration. Two regression tests cover
the path:

  - test_bit_vector_range_iterator_bounds() exercises the iterator
    directly with two hand-crafted bitmaps (same-word and multi-word
    search regions). Without the fix EXPECT_LT(range_start, end)
    fails on the first iterator call.

  - test_jit_allocator_search_end_bounds() drives JitAllocator into
    the production state (area_used = area_size - 14, free bits both
    inside the search range and at the tail past _search_end). With
    the bug the alloc trips the existing offset <= block_size - size
    assertion at line ~999; with the fix the search reports no fit and
    a fresh block is allocated.
@puzpuzpuz puzpuzpuz added the bug Something isn't working label May 27, 2026
@bluestreak01 bluestreak01 merged commit 0054ff4 into master May 27, 2026
193 checks passed
@bluestreak01 bluestreak01 deleted the fix/jit-allocator-search-end-bounds branch May 27, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants