DF-2849 / verdict.json
{ "finding_id": "DF-2849", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Race-triggered kernel panic reproduced and captured on the stock INVARIANTS kernel: a kernel thread doing plain GROUPTASK_ENQUEUE() against concurrent taskqgroup_attach/detach churn hits the window where taskqgroup_detach() has already stored gtask->gt_taskqueue = NULL (subr_gtaskqueue.c:746) but the task object is still shared, and grouptaskqueue_enqueue() panics ('panic: queue == NULL', backtrace grouptaskqueue_enqueue+0x11d <- racer, see panic.txt). On a non-INVARIANTS kernel the same interleaving is TQ_LOCK(NULL) -> kernel page fault. The second face of the same defect - the unlocked non-atomic 'ta_flags &= ~TASK_NOENQUEUE' at line 747 racing the locked 'ta_flags |= TASK_ENQUEUED' at lines 218-219, which can drop TASK_ENQUEUED while the gtask is linked and make the next enqueue double-insert it into the STAILQ (self-loop -> worker re-runs the task forever, queue corruption) - is a certain code-level race but was not empirically observed (2-cycle window; clean 200k-cycle runs; heavier harnesses starve the guest for their own reasons, documented in run.2.log). Reachability: the taskqgroup API has zero in-tree consumers, so both modes require root-KLD misuse or future API adoption - same latent family as DF-0085.", "exploit_chain": "", "evidence": [ "panic.txt: 'panic: queue == NULL' + 'grouptaskqueue_enqueue() at grouptaskqueue_enqueue+0x11d' + 'racer() at racer+0x2f' on cpuid 1", "run.log: stock-kernel gtq_race stats (enq_null=896739 NULL-window hits in 30s - each is a panic lottery ticket on the unfixed kernel)", "run.fixed.log: fixed kernel, same gtq_null churn 25s+120s: no panic, guest up", "run.2.log (tail): fixed kernel gtq_race 200k cycles, fn_count 6109 -> 6109 frozen post-stop, clean unload", "fixbuild.log: fixed kernel #1 build+install BUILD_OK" ], "kernel_refs": [ "sys/kern/subr_gtaskqueue.c:746", "sys/kern/subr_gtaskqueue.c:747", "sys/kern/subr_gtaskqueue.c:218", "sys/kern/subr_gtaskqueue.c:219", "sys/kern/subr_gtaskqueue.c:203", "sys/kern/subr_gtaskqueue.c:204" ], "poc_changes": "Added #include <sys/conf.h> for DEV_MODULE; created three variants because the seed sketch did not exist (seed was a KLD-harness hint only): gtq_null.c (mode-1 panic trigger), gtq_race.c (lightweight stats churn), gtq_race2.c (4-racer corruption hunter with lwkt_yield - later learned that yielding daemon-priority spinners still starves userland, so race2 runs are wedges to be ignored); generated-header symlinks needed for out-of-tree KLD builds.", "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": 4200, "guest_dirty": 0, "build_cmd": "cd /root/poc && make -f Makefile.null -m /usr/share/mk SYSDIR=/usr/src/sys", "run_cmd": "kldload /root/poc/gtq_null.ko (stock kernel: panic within seconds)", "code_hash": "4514d78ed83762bb461156a15cf8ea3926f7c0d44c087aa0eb88b94f8529895a", "notes": "Mode-2 (lost update) not empirically observed - 2-cycle window; classify that component speculative. The race2 4-racer harness wedges the guest by CPU starvation on BOTH stock and fixed kernels - do not use those runs as evidence either way. Guest reset to clean snapshot after run.", "recommended_fix": "Keep TASK_NOENQUEUE set in taskqgroup_detach (drop the unlocked clear); re-arm it under the taskqueue lock in taskqgroup_attach/attach_cpu; make grouptaskqueue_enqueue return EINVAL for a NULL queue.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly 6.5-DEVELOPMENT #1: Wed Sep 2 09:11:43 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (4 hunks) applied in-guest, kernel rebuilt/installed (#1 Wed Sep 2 09:11:43 UTC 2026). Exact PoC re-run on fixed kernel: gtq_null churn 25s+120s plus 15+ min inside the race2 soak with NO panic (baseline stock kernel panicked in seconds); gtq_race full 200k-cycle run completes with post-stop fn_count frozen and clean kldunload. Mode 2 closed by construction: all ta_flags mutations are now under the owning taskqueue lock, so TASK_ENQUEUED cannot be lost while the task is linked (no double-insert/self-loop path remains).", "fix_evidence": [ "run.fixed.log: guest up after 25s/120s of the churn that panicked stock", "run.2.log tail: 'enq_ok=1685324 ... fn_count=6109 -> 6109' + FIXED_KERNEL_UNLOAD_OK", "fixbuild.log: '>>> Kernel install for X86_64_GENERIC completed' + BUILD_OK" ] } |