# DF-2872 — taskqueue_free() ignores armed timeout callouts → post-free UAF

## What
`taskqueue_free()` never consults `queue->tq_callouts` (the counter is
write-only in the whole file: incremented at subr_taskqueue.c:359,
decremented at :335/:480, read nowhere). If a queue is freed while a
`timeout_task` callout is armed, the callout fires later and
`taskqueue_timeout_func()` (:323-338) dereferences
`timeout_task->t.ta_queue`, which points at the **freed** queue: it takes
the freed queue's spinlock, decrements its `tq_callouts`, and STAILQ-inserts
the task into the freed (here: reallocated, LIVE) queue.

The harness arms a 5-tick callout, frees the queue, then forces a new
taskqueue to alias the freed memory (objcache LIFO, same zone/size). The
stray callout then enqueues the timeout task onto the NEW LIVE queue, which
executes it — decisive cross-object use-after-free.

In-tree hazard: wlan's scan timeout task lives on `ic->ic_tq`
(ieee80211_scan_sw.c:1023) and `ic->ic_tq` is `taskqueue_free`'d at
ieee80211.c:464; `ieee80211_scan_detach()` never cancels it.

## Build / Run
see build.sh / run.sh

## Expected output (stock kernel — reproduced)
```
tqto: step3 armed 5-tick callout (res 0); freeing q1 WITHOUT cancel_timeout
tqto: step4 q1 FREED while callout armed; reallocating...
tqto: step5 alias loop done i=0 q2=0xfffff80117bcd180
tqto: q2=0xfffff80117bcd180 alias of freed q1: YES
tqto: TIMEOUT TASK RAN (run 1) on queue 0xfffff80117bcd180
      (q1 was 0xfffff80117bcd180, q2 is 0xfffff80117bcd180)
      <== REALIASED (FREED) QUEUE: UAF CONFIRMED
```
Writes performed by the kernel into freed-then-reallocated memory during
this run: `q->tq_callouts--` (live q2 corrupted to -1), `f &= ~ARMED`,
`STAILQ_INSERT_TAIL(&q->tq_queue, ...)` into q2's list, spin_lock on q2's
tq_lock.

## Fixed kernel
taskqueue_free() waits for tq_callouts == 0 after clearing TQ_FLAGS_ACTIVE;
the callout fires while the queue is still allocated and its enqueue is
rejected with EPIPE → the timeout task never runs after free (harness prints
"callout did not run in 5s?!" — which on the fixed kernel is exactly the
correct outcome). See run.fixed.log.
