⬢ DragonFlyBSD Kernel Audit
DF-2870 / verdict.json
← back to finding ↓ download raw
{
  "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 guests 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."
}