DF-2870 / verdict.json
{ "finding_id": "DF-2870", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "100% deterministic permanent kernel deadlock. taskqueue_free() called while any task of the queue is in-flight never returns: taskqueue_terminate()'s wakeup(tq) (subr_taskqueue.c:144, fired after TQ_UNLOCK) lands while the worker executes ta_func (not on the sleep queue) and is lost; when ta_func returns, taskqueue_thread_loop() parks in TQ_SLEEP(tq,tq) at :619 without re-checking TQ_FLAGS_ACTIVE, and the terminator is already asleep on tq_threads at :146 - both sleep forever with no timeout and no remaining waker (post-free enqueues return EPIPE at :199, so taskqueue_thread_enqueue never fires). Guest proof: harness blocks ta_func on a flag, calls taskqueue_free, releases the func from a third thread; RESULT: DEADLOCK CONFIRMED - free stuck 10+s, ps -axH shows tqdeadctrl in wchan 'taskqueue_terminate' and the worker in 'tqthr' permanently. Every in-tree taskqueue_free() consumer (wlan ieee80211.c:464 plus ~15 drivers) wedges its calling thread this way when freeing a still-active queue (detach under load -> local DoS). fix.diff (re-check TQ_FLAGS_ACTIVE under the held spinlock between run and sleep) validated: 'taskqueue_free RETURNED (not reproduced)', no wedged threads, module unloads cleanly.", "exploit_chain": "", "evidence": [ "run.log: 'RESULT: DEADLOCK CONFIRMED - taskqueue_free() stuck for 10+ seconds'", "run.log ps: 'B2 taskqueu tqdeadctrl' + 'B4 tqthr tqdead' (both parked forever)", "run.fixed.log: 'taskqueue_free RETURNED (not reproduced)' + NO-TQDEAD-THREADS + clean unload" ], "kernel_refs": [ "sys/kern/subr_taskqueue.c:141", "sys/kern/subr_taskqueue.c:144", "sys/kern/subr_taskqueue.c:146", "sys/kern/subr_taskqueue.c:153", "sys/kern/subr_taskqueue.c:155", "sys/kern/subr_taskqueue.c:617", "sys/kern/subr_taskqueue.c:619" ], "poc_changes": "initial DECLARE_MODULE build GPFs this guest's kldload (loader quirk; DEV_MODULE used instead); harness watchdog originally mis-initialized (hit_thr=-1 made the loop a no-op) - fixed before the recorded run", "attempts": 2, "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": 6900, "guest_dirty": 0, "build_cmd": "cd /root/poc/tqdead && make -m /usr/share/mk SYSDIR=/usr/src/sys (see build.sh)", "run_cmd": "kldload /root/poc/tqdead/tqdead.ko; sleep 15; dmesg | grep tqdead; ps -axH -o stat,wchan,comm | grep tqdead (see run.sh)", "code_hash": "8b9629c14a1e16e6611285be85f51d54932bef8ab7bfe5151cdd7cf28ac8d638", "fix_status": "fixed", "fix_kernel_uname": "DragonFly 6.5-DEVELOPMENT #1: Wed Sep 2 17:34:00 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Combined fix.diff (per-task TASKQ_RUNNING under TQ_LOCK + wakeup after unlock in taskqueue_run; ACTIVE re-check before worker park; tq_callouts wait in taskqueue_free; timeout_func freer wakeup) built as kernel #1 Wed Sep 2 17:34:00. Baseline bad behavior GONE on every PoC: DF-2869 cancel=EBUSY + drain blocks + no UAF/panic + clean unload; DF-2870 taskqueue_free returns, no wedged threads; DF-2872 timeout task no longer runs after free; DF-2873 hammer does not lose a wakeup (bounded negative). Guest left clean via vm.sh reset with-src.", "fix_evidence": [ "run.fixed.log" ], "notes": "wedged threads are permanent - guest reset performed after evidence capture", "recommended_fix": "Re-check TQ_FLAGS_ACTIVE under the queue spinlock between taskqueue_run() and TQ_SLEEP in taskqueue_thread_loop() so a wakeup delivered while ta_func runs cannot be lost." } |