{
 "finding_id": "DF-2869",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "Deterministic premature-free UAF proven on the stock INVARIANTS kernel: with two threads servicing one taskqueue (API-supported count>=2), completion of a fast task clears the single tq_running slot while a slow task is still executing, so taskqueue_cancel() returns 0 instead of EBUSY and taskqueue_drain() returns immediately (subr_taskqueue.c:407/:411/:429/:493). The harness then follows the documented cancel/drain-then-free contract, kfree()s the running task's context, forces objcache-LIFO reallocation of the same address (printed alias YES), and the still-running ta_func reads the overwritten memory (magic 0xdeadbeef) -> panic with the backtrace taskqueue_thread_loop -> taskqueue_run -> t1_func, i.e. the taskqueue runner executing on freed-and-reused kernel memory. Primitive: slab-groomable free-while-running (UAF read+write of task-owned context of attacker-chosen consumer objects). No unprivileged in-tree trigger on this guest: the co-runner precondition needs a count>=2 queue (in-tree: vmxnet3 if_vmx.c:990-997, torn down by an admin) or the taskqueue_free() co-run window (subr_taskqueue.c:155); reaching the victim task from userspace requires such a consumer under attack-driven load. That is a reach blocker, not a primitive blocker - the corruption itself is proven. uid0 chain therefore not exercised end-to-end; the natural chain is: drive victim driver's multi-thread queue under load, race its detach drain, free its per-queue softc while the deferred task runs, reallocate same-zone, corrupt function-pointer-bearing state, hijack. fix.diff (per-task TASKQ_RUNNING under TQ_LOCK + wakeup after unlock) validated: cancel returns EBUSY=16, drain blocks until ta_func completes, no panic, module unloads cleanly.",
 "exploit_chain": "proven: 2-runner queue -> tq_running clobbered -> cancel=0 + drain early-return -> kfree(live ctx) + same-size kmalloc (LIFO alias) -> running ta_func reads/writes foreign object (magic flip observed) -> controlled panic. uid0 route (not exercised, reach-blocked): vmxnet3-style count>=2 consumer torn down under rx/tx load -> premature free of driver softc during detach drain -> slab groom -> function-pointer corruption -> code exec in ring0.",
 "evidence": [
  "run.log/panic.txt: 'taskqueue_cancel(t1) = 0 while t1 running' + 'taskqueue_drain(t1) returned while t1 ta_func still executing'",
  "run.log/panic.txt: 'freed ctx reallocated at 0xfffff8008d2e0810 (alias YES) magic=deadbeef' + 'UAF CONFIRMED'",
  "panic.txt: panic trace t1_func+0x9b <- taskqueue_run+0xbb <- taskqueue_thread_loop+0x5d",
  "run.fixed.log: 'taskqueue_cancel(t1) = 16' + releaser observes drain() blocking + clean unload, no panic"
 ],
 "kernel_refs": [
  "sys/kern/subr_taskqueue.c:407",
  "sys/kern/subr_taskqueue.c:411",
  "sys/kern/subr_taskqueue.c:429",
  "sys/kern/subr_taskqueue.c:493",
  "sys/kern/subr_taskqueue.c:512",
  "sys/kern/subr_taskqueue.c:155",
  "sys/dev/virtual/vmware/vmxnet3/if_vmx.c:997"
 ],
 "poc_changes": "Baseline evidence was produced with the initial harness (identical bug path); the final source adds a releaser thread + clean-shutdown path so the FIXED kernel run terminates without wedging (first fixed-kernel attempt left the controller blocked inside the now-correctly-blocking drain and kldunload crashed - harness flaw, not a fix failure, documented in VERDICT.md). Also: taskqueue_start_threads name must not carry %d (auto-suffixed), DECLARE_MODULE/MODULE_VERSION modules GPF this guest's kldload - DEV_MODULE used instead (see deploy_guest.sh).",
 "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": 6900,
 "guest_dirty": 0,
 "build_cmd": "cd /root/poc/tquaf && make -m /usr/share/mk SYSDIR=/usr/src/sys (with device_if.h/bus_if.h symlinks; see build.sh)",
 "run_cmd": "kldload /root/poc/tquaf/tquaf.ko; sleep 6; dmesg | grep tquaf  (see run.sh)",
 "code_hash": "49dd949d0e1b8fef6c8d07b172513c13fc2bda80a98f0485894ab4d5a68d8a76",
 "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. NOTE: one intermediate fixed-kernel run crashed because the module had been rebuilt against the STOCK ABI while struct task grew by ta_flags - module-build logistics, not a fix defect; final run used modules built against the patched /usr/obj.",
 "fix_evidence": [
  "run.fixed.log"
 ],
 "notes": "guest reset (vm.sh reset with-src) performed after final run; combined fix.diff shared with DF-2870/2872/2873 packs",
 "recommended_fix": "Track running tasks with a per-task TASKQ_RUNNING flag set at dequeue and cleared under the queue spinlock (wakeup after unlock); make taskqueue_cancel/taskqueue_drain(_simple) consult the flag instead of the single tq_running slot."
}