DragonFlyBSD Kernel Audit
DF-2872 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-2872",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "dos",
 "confidence": "certain",
 "verdict": "Deterministic post-free use-after-free: taskqueue_free() (subr_taskqueue.c:150-166) never reads queue->tq_callouts - the counter is write-only in the entire file (:359 ++, :335/:480 --), so freeing a queue with an armed timeout_task callout leaves a live callout whose taskqueue_timeout_func() (:323-338) then locks the FREED queue's spinlock, decrements its tq_callouts, and STAILQ-inserts its task into the freed queue. Guest proof: arm a 5-tick callout on q1, free q1, immediately taskqueue_create() (same zone/size, objcache LIFO) - q2 lands on q1's exact address (alias YES at iteration 0); the stray callout fires and executes the timeout task ON the reallocated live q2 (printed), i.e. the kernel wrote q->tq_callouts-- (q2 corrupted to -1), cleared f flags and linked the task into q2's list through freed-then-reused memory. Cross-object heap corruption family; no panic coaxed (fields not lethal in this shape), hence dos-class impact with the write primitives enumerated. In-tree hazard: wlan's scan timeout task (ieee80211_scan_sw.c:1023) lives on ic_tq which is taskqueue_free()d at ieee80211.c:464 and is never cancelled by ieee80211_scan_detach(). fix.diff (free waits for tq_callouts==0 after clearing ACTIVE; timeout_func wakes the freer at 0; late enqueues rejected with EPIPE) validated: timeout task does NOT run after free, module unloads cleanly.",
 "exploit_chain": "proven corruption: callout -> freed queue object -> same-zone reallocation -> tq_callouts-- / flag RMW / STAILQ_INSERT_TAIL into live victim object. Groomable to stronger corruption if the freed queue's memory is reused by a function-pointer-bearing object (taskqueue_create is only the demonstration vehicle).",
 "evidence": [
  "run.log: 'q2=0xfffff80117bcd180 alias of freed q1: YES'",
  "run.log: 'TIMEOUT TASK RAN (run 1) on queue ...180 <== REALIASED (FREED) QUEUE: UAF CONFIRMED'",
  "run.fixed.log: alias still YES but 'callout did not run in 5s' (correct fixed behavior) + clean unload"
 ],
 "kernel_refs": [
  "sys/kern/subr_taskqueue.c:62",
  "sys/kern/subr_taskqueue.c:150",
  "sys/kern/subr_taskqueue.c:155",
  "sys/kern/subr_taskqueue.c:165",
  "sys/kern/subr_taskqueue.c:323",
  "sys/kern/subr_taskqueue.c:330",
  "sys/kern/subr_taskqueue.c:335",
  "sys/kern/subr_taskqueue.c:359",
  "sys/netproto/802_11/wlan/ieee80211.c:464",
  "sys/netproto/802_11/wlan/ieee80211_scan_sw.c:1023"
 ],
 "poc_changes": "none beyond the DEV_MODULE registration fix (DECLARE_MODULE modules GPF this guest's kldload)",
 "attempts": 1,
 "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/tqto && make -m /usr/share/mk SYSDIR=/usr/src/sys (see build.sh)",
 "run_cmd": "kldload /root/poc/tqto/tqto.ko; sleep 8; dmesg | grep tqto; kldunload tqto (see run.sh)",
 "code_hash": "d2e1f9acaa5fa19353f9b54368330585411b48e38c848355d729a015d5135213",
 "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": "first-ever kldload attempt crashed in the guest's module loader (strcmp GPF) - traced to the guest's DECLARE_MODULE/MODULE_VERSION path, not to the PoC; DEV_MODULE pattern (proven by DF-2848) used instead",
 "recommended_fix": "taskqueue_free() must wait for queue->tq_callouts == 0 (after clearing TQ_FLAGS_ACTIVE so late callout enqueues get EPIPE) and taskqueue_timeout_func() must wake the waiter when the count reaches zero."
}