Dead terminator guard in blst_meta_free/blst_meta_fill checks parent's bm_bighint instead of child's scan[i] β out-of-range free/fill walks uninitialized terminator slots and can index past bl_rootblks
| Field | Value |
|---|---|
| ID | DF-2789 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H |
| CWE | CWE-697 (latent CWE-787 / CWE-457) |
| File | sys/kern/subr_blist.c |
| Lines | 660, 775 (correct pattern :483/:519/:612/:747/:853) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
blst_meta_free and blst_meta_fill guard the child-walk loop with
if (scan->bm_bighint == (swblk_t)-1) panic(...) β but scan is the
CURRENT (parent) node, which by construction is never a terminator. All
seven other walks correctly test the CHILD slot. The intended
"freeing/filling unexpected range" panic is dead code. A free or fill
whose range extends past bl_blocks walks onto and past the terminator
slot: the terminator's union is uninitialized heap (bl_root allocated
without M_ZERO; radix_init only sets bm_bighint=-1 on terminators),
garbage is consumed as avail/bitmap state, recursion continues past
bl_rootblks, and the leaf bitmap |= / &= ~mask writes out of bounds of
the M_SWAP allocation. Latent only: every in-tree caller passes
in-range arguments β the missing guard converts a future caller bug
from a clean panic into heap corruption in the swap allocator.
Recommended fix
Test the child slot (scan[i]) exactly like every other walk β two-line diff in the row above.
Timeline
- 2026-08-31 Discovered during pass-2 audit of subr_blist.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2789 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| v26harness.c | β | 14.3 KB | view raw | |
| run.log | β | 1.5 KB | view raw | |
| fix.diff | β | 584 B | view raw |
Fix verification
fixedPatched kernel: the child-guard fix makes the out-of-range free(128,64) trigger the DESIGNED clean panic blst_meta_free: freeing unexpected range (baseline: silent corruption, out-of-range grants, double grants, and a 16-byte heap OOB write past bl_root).
cmd=3 on patched kernel: panic: blst_meta_free: freeing unexpected range
Confirmed kernel references
Detail
Exploit chain
blist_free(out-of-range) -> guard checks parent (dead) -> walk into terminator/uninitialized slots -> phantom bits freed -> out-of-range + double-granted blocks AND 16-byte OOB write 496B past bl_root
Evidence (decisive lines)
v26harness.c t3/t7/t8; df2789_mod.log: NO designed panic, OUT-OF-RANGE grant x2, double-grant x2, root[100] 0->0x800 OOB write
PoC changes
fresh blists are born all-allocated (radix_init); the standard init is blist_free(bl,0,bl_blocks) (vm_swap.c:380 pattern); blist_alloc caps at BLIST_MAX_ALLOC=64
Verified recommended fix
--- sys/kern/subr_blist.c 2026-06-29 12:51:19.000000000 +0000
Verdict
Fully reproduced all three layers of the claim with a kernel harness. (1) Dead guard: blst_meta_free and blst_meta_fill check the PARENTs bm_bighint (sys/kern/subr_blist.c:660-661, :775-776) instead of the child scan[i]; an out-of-range blist_free(128, 64) on a blist(65) whose real blocks are all allocated returned silently - the designed freeing unexpected range panic is dead code. (2) Logical corruption: the walk freed terminator-slot bits, then blist_alloc(64) GRANTED block 128 - beyond bl_blocks=65 - and a second phantom free+alloc granted 128 AGAIN to a second holder (overlapping allocation; on the swap blist this maps two pages onto one swap slot). Same results at scale on blist(2081): out-of-range grant of 2112 and double-grant. (3) Physical OOB write: blist_free(6144, 2048) walks root child index 100, which is 496 bytes PAST the 1104-byte bl_root allocation (rootblks=69); bl_root[100].avail and .bm_bighint changed from 0 to 0x800 as a direct result of the call - a deterministic heap OOB write into adjacent slab memory. An ungroomed first attempt panicked in blst_leaf_free: freeing free block on garbage terminator bitmaps, proving the walk consumes uninitialized heap. All in-tree callers pass in-range ranges (verified), so this is latent in-tree - but the missing guard converts any future caller bug from a clean panic into silent heap corruption.
No comments yet.