β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2789

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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2789 Β· 3 files
FileTypeDescriptionSize
v26harness.c β€” 14.3 KB view raw
run.log β€” 1.5 KB view raw
fix.diff β€” 584 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched 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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Oct 24 17:14:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (v26 fix-all kernel; sys_generic.c fix-v2 rebuild pending A/B)

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.