DF-2915 / verdict.json
{ "finding_id": "DF-2915", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED, both as root-cause and end-to-end. sleepq_lock() (sys/kern/subr_sleepqueue.c:208-209) assumes objcache_get() returns zeroed sleepqueue_wchan objects, but sleepq_wc_cache is an objcache_create_simple() cache whose allocator objcache_malloc_alloc() (kern_objcache.c:571-577) kmalloc()s without M_ZERO and registers no ctor (kern_objcache.c:386-391). On the stock kernel a probe that pollutes the M_SLEEPQ zone with 0xAA and rebuilds the identical cache saw 256/256 objcache_get() objects violating the KKASSERT precondition (uninitialized wc_wchan/wc_refs/wc_blocked). End-to-end: on a kernel with only the known DF-0139 hash bug masked (test-only precondition; without it sleepq_lock GPFs in 'lock xaddl %edx,sleepq_chains(%r13)' first - captured in panic_df0139_stockkernel.txt), the first real sleepq_lock/sleepq_add/sleepq_wait sequence panics with 'assertion \"wc->wc_wchan == NULL && wc->wc_refs == 0\" failed in sleepq_lock at subr_sleepqueue.c:209' (panic.txt). On production (non-INVARIANTS) kernels the KKASSERT is compiled out and the garbage wc (wc_wchan != NULL) enters sc_wchead where it can never match a wchan nor serve as a free slot, so the for(;;) re-search loop allocates again - unbounded M_SLEEPQ growth/livelock plus permanently stuck entries and inflated sc_free_count. Fix validated: with fix.diff (bzero after objcache_get) the identical trigger runs clean through lock/add/wait/broadcast on the rebuilt kernel. Reachability: the file is compiled into every kernel (sys/conf/files:1453) but has ZERO in-tree callers; it exists for FreeBSD-compat/Linux-KPI kld modules, so triggering requires loading a consumer module (root-only). No unprivileged path; no privesc chain exists for this bug class.", "exploit_chain": "", "evidence": [ "run.log - stock-kernel probe: 'SQDEMO probe: 256/256 objcache_get() objects violate the KKASSERT precondition at subr_sleepqueue.c:209'", "panic.txt - end-to-end on mask-only kernel #1: panic assertion wc->wc_wchan == NULL && wc->wc_refs == 0 at subr_sleepqueue.c:209, backtrace sleepq_lock+0x200 <- sq_sleeper+0x10", "panic_df0139_stockkernel.txt - stock kernel: Fatal trap 9 GPF at sleepq_lock+0x54 'lock xaddl %edx,sleepq_chains(%r13)' re-demonstrating why DF-0139 must be masked to reach the DF-2915 path", "../DF-2917/run.log - kernel #2 (mask+fix2915): same kldload + 0xAA pollution, kldload-rc=0, NO panic, module completes - fix validated", "VERDICT.md - full narrative incl. production-kernel (non-INVARIANTS) manifestation analysis" ], "kernel_refs": [ "sys/kern/subr_sleepqueue.c:145", "sys/kern/subr_sleepqueue.c:185", "sys/kern/subr_sleepqueue.c:208", "sys/kern/subr_sleepqueue.c:209", "sys/kern/subr_sleepqueue.c:212", "sys/kern/kern_objcache.c:378", "sys/kern/kern_objcache.c:386", "sys/kern/kern_objcache.c:571", "sys/kern/kern_objcache.c:582" ], "poc_changes": "Authored from scratch (no seed PoC - new pass-2 finding). Key hurdles: (1) DF-0139's wild hash index panics spin_lock() before the DF-2915 path is reachable, so a TEST-ONLY mask patch (test_df0139_mask.diff) was applied in the guest's /usr/src; (2) 'make nativekernel' builds but does NOT install - 'make installkernel' is required or the box keeps booting the old kernel; (3) kmod build details: kthread_create needs <sys/kthread.h>, sbticks is already declared in <sys/kernel.h> (redeclaring trips -Werror=redundant-decls); (4) M_SLEEPQ is 'struct malloc_type M_SLEEPQ[1]' so modules must extern it as an array; (5) module .ko lands in the obj dir (/usr/obj/tmp/...) and /tmp is wiped on reboot - copy to /root.", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 2820, "guest_dirty": 0, "build_cmd": "sh /root/sqdemo/build.sh (modules) ; cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC (test kernels)", "run_cmd": "kldload /usr/obj/tmp/sqdemo/sqprobe/sqprobe.ko (probe, stock) ; kldload /root/sqe2e.ko (e2e trigger)", "code_hash": "9fa6c75f5ee70aa5f6a44b564f087fe8b4bbab8fcb52b41c45aa0a0ef5f47f89", "notes": "Code hash is sha256 of sqe2e.c (primary trigger); sqprobe.c = 61639636bd8cf8079ed757c071b96843d1bcf34a382c89d0ab1f5e4c03d8fbf6. Kernel sequence: stock #0 (Jul 2) -> #1 Sep 3 09:02:18 (DF-0139 mask only: PANIC at :209) -> #2 Sep 3 09:16:39 labelled '#1' (mask+DF-2915 fix: clean) -> #3 Sep 3 09:26:13 labelled '#2' (mask+2915+2917 fixes: clean, real sleeping). kbuild2_fix2915.log holds the full untrimmed fix-kernel build. Production-kernel (non-INVARIANTS) garbage-chain/alloc-loop manifestation is source-derived; executing it would need a non-INVARIANTS build (out of scope, mechanism fully determined).", "recommended_fix": "Zero the freshly allocated wc in sleepq_lock(): replace the KKASSERT at subr_sleepqueue.c:209 with bzero(wc, sizeof(*wc)) (or create sleepq_wc_cache with a zeroing allocator/ctor).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Thu Sep 3 09:16:39 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (bzero(wc, sizeof(*wc)) replacing the load-bearing KKASSERT) applied to the guest /usr/src tree on top of the DF-0139 test mask; kernel rebuilt (kbuild2_fix2915.log, rc=0) and installed. The exact trigger that panicked the baseline kernel (same sqe2e.ko, same 0xAA pollution of M_SLEEPQ, same first-use sleepq_lock) loads and completes the full lock/add/wait/broadcast sequence with no panic and the guest stays up. fix_status: fixed.", "fix_evidence": "kbuild2_fix2915.log (full build log); ../DF-2917/run.log lines: 'kldload-rc=0', 'SQDEMO e2e: M_SLEEPQ polluted with 0xAA...' followed by completed round trips on the fixed kernel; panic.txt shows the same trigger on the unfixed kernel." } |