# DF-2869 — taskqueue tq_running single-slot race → cancel/drain miss running task → UAF

## What
`taskqueue_run()` (sys/kern/subr_taskqueue.c) tracks the currently-executing
task in the single `queue->tq_running` slot and clears it **outside** the
queue spinlock. With ≥2 concurrent runners on one queue the completion of
task B clobbers/clears `tq_running` while task A is still executing, so:

* `taskqueue_cancel()` fails to return `EBUSY` for the running task, and
* `taskqueue_drain()` / `taskqueue_drain_simple()` return immediately

while `ta_func(A)` is still on the CPU. A caller following the documented
cancel/drain-then-free contract then frees A's task/context → use-after-free
(read + write) executed from inside the taskqueue runner.

Concurrent runners exist in-tree: `taskqueue_start_threads(count>=2)` is an
API-supported configuration (vmxnet3 uses nthreads = ntxqueues/2 ≥ 2,
sys/dev/virtual/vmware/vmxnet3/if_vmx.c:990-997, and drains those tasks at
if_vmx.c:1012), and `taskqueue_free()` co-runs the queue with the service
thread (subr_taskqueue.c:155 racing the worker's run loop).

## Build (on the guest as root)
```
cd /root/poc/tquaf
ln -sf /usr/obj/usr/src/sys/X86_64_GENERIC/device_if.h .
ln -sf /usr/obj/usr/src/sys/X86_64_GENERIC/bus_if.h .
make -m /usr/share/mk SYSDIR=/usr/src/sys
```

## Run
```
kldload /root/poc/tquaf/tquaf.ko
sleep 6
dmesg | grep tquaf        # then read the serial console for the panic
```

## Expected output (stock kernel — reproduced)
```
tquaf: queue 0xfffff8011756aec8 started (0)
tquaf: taskqueue_cancel(t1) = 0 while t1 running (0 = contract violated, EBUSY=16 expected)
tquaf: BUG PROVEN: taskqueue_drain(t1) returned while t1 ta_func still executing
tquaf: freeing live task context 0xfffff8008d2e0810
tquaf: freed ctx reallocated at 0xfffff8008d2e0810 (alias YES) magic=deadbeef
tquaf: UAF CONFIRMED: running task sees ctx 0xfffff8008d2e0810 magic=deadbeef (expected cafebabe)
panic: DF-2869: task context freed while task running (magic=deadbeef)
Trace: t1_func() at t1_func+0x9b
       taskqueue_run() at taskqueue_run+0xbb
       taskqueue_thread_loop() at taskqueue_thread_loop+0x5d
```
The panic backtrace is the proof: `taskqueue_run` → `t1_func` executing on
memory that was freed and reallocated (objcache LIFO aliasing forced by the
harness).

## Fixed kernel
`taskqueue_cancel` returns EBUSY, `taskqueue_drain` blocks until the func
completes → harness prints "NOT REPRODUCED: drain blocked correctly", no
panic (see fix.diff / run.fixed.log).
