Fix two frame-allocator soundness bugs and a kernel-stack overrun - #72
Draft
chbaker0 wants to merge 2 commits into
Draft
Fix two frame-allocator soundness bugs and a kernel-stack overrun#72chbaker0 wants to merge 2 commits into
chbaker0 wants to merge 2 commits into
Conversation
find_bit_group's mask was (len << 1) - 1 instead of (1 << len) - 1, and fill_bitmap_from_map mishandled Available ranges contained in one bitmap byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EgAWuxTU8rZUj7r9Eicwq1
create_task allocated 2 frames but placed the initial stack contents at stack_bottom + STACK_LEN (8 frames), writing 24 KiB past the allocation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EgAWuxTU8rZUj7r9Eicwq1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Filed by an AI coding agent (Claude Code).
Found during a scheduled security scan of the repo. Three bugs, all of which break the
unsafe trait FrameAllocatorinvariant "allocations do not return allocated or reserved frames" or write outside an allocation. Each is covered by a regression test that fails onmaster.1.
find_bit_grouptested 3 bits instead of 4 (shared/src/memory/alloc/phys.rs:364)This is
2*len - 1, not2^len - 1. It coincides with the intended value forlen1 and 2, but forlen == 4it produces0b111— soallocate_range(2)accepted a group where only 3 of the 4 frames were free, then cleared all 4 bits and handed out a frame that was already allocated.29 of the 256 possible bitmap bytes trigger it. Example:
0b01110000has frames 4,5,6 free and frame 7 in use; the old code returnedSome(4)and handed out frames 4–7.Reachable in the live kernel:
mm::HeapProvider::allocatecomputesorderfromnum_chunks.next_power_of_two(), so a heap allocation spanning 3–4 chunks callsallocate_range(2).Fixed to
((1u16 << len) - 1) as u8.2.
fill_bitmap_from_mapmishandled ranges inside a single byte (shared/src/memory/alloc/phys.rs:299)The leading/trailing partial-byte logic assumed a range spans at least one 8-frame boundary. For an
Availablerange entirely inside one bitmap byte, both partial branches fired and marked frames free that weren't in the map at all:end_aligned - 1withend_aligned == 0, i.e. au64underflow (panics withoverflow-checks, and with them off the followingassert_eq!fails anyway).Small conventional-memory regions of a few pages are ordinary in real UEFI memory maps, so this is reachable on real firmware rather than only in theory.
Rewrote the loop in terms of the inclusive last frame, with an explicit same-byte case. All pre-existing
fill_bitmap_*tests still pass unchanged.3. Kernel stacks were 4× smaller than
STACK_LENclaims (src/sched.rs)Three numbers disagreed:
create_task→allocate_owned_frames(1)STACK_FRAMES = 2 << STACK_FRAMES_ORDER(order 2)STACK_LEN = STACK_FRAMES * PAGE_SIZEcreate_taskallocates 2 frames (8 KiB) but setsstack_top = stack_bottom + STACK_LEN(32 KiB), so every task'sTaskdescriptor and initial stack contents land 24 KiB past the end of its own allocation — into whatever the frame allocator handed out next. This is on the live boot path (init_kernel_main_thread→create_task, plus everyspawn_kthread).2 << orderis also2^(order+1), not2^order— the frame allocator's actual contract.Fixed by making
STACK_FRAMES_ORDERthe single source of truth (= 3),STACK_FRAMES = 1 << STACK_FRAMES_ORDER, and havingcreate_taskpassSTACK_FRAMES_ORDER.STACK_LENstays 32 KiB, so observable stack size is unchanged; the allocation now matches it. Added aconst_assert_eq!tying the two together.This is the one judgment call in the PR: preserving
STACK_LENwas the choice that keeps behavior stable, but you could equally have wanted 4 frames (keepingSTACK_FRAMES_ORDER = 2and shrinkingSTACK_LENto 16 KiB). Worth a look.Test plan
cargo stest— 58 pass (54 pre-existing + 4 new). All 4 new tests fail onmaster; verified individually before fixing.cargo kcheck/lcheck/icheck— clean.cargo kclippy/sclippy— clean (no denied-lint violations).qemu-system-x86_64isn't available in this container, so bug 3's fix is unverified on real boot — it's the change most worth confirming that way, since it alters what the very firstcreate_taskallocates. CI'ssmokejob covers it.cargo smiri(CI'sexpensivejob covers it; none of these changes touch the paging pointer walks).Generated by Claude Code