# DF-4271 — wb_stop()/wb_detach() use non-interlocked callout_stop() against a self-re-arming wb_tick → post-detach UAF

Files: `sys/dev/netif/wb/if_wb.c:1590` (callout_stop in wb_stop),
`:839-846` (detach under serializer), `:1191-1201` (wb_tick).

## What

```c
static void
wb_tick(void *xsc)
{
        ...
        lwkt_serialize_enter(ifp->if_serializer);      /* :1197 - self-serializing */
        mii_tick(mii);
        callout_reset(&sc->wb_stat_timer, hz, wb_tick, sc);   /* :1199 - re-arms itself */
        lwkt_serialize_exit(ifp->if_serializer);
}
```

`wb_stop()` (serialized context) calls `callout_stop(&sc->wb_stat_timer)`
(:1590) only. Per `sys/kern/kern_timeout.c` (callout_stop documentation for
non-lock-aided callouts — wb uses plain `callout_init()`, :715): *"Stops a
callout if it is pending or queued, does not block. This function does not
interlock against a callout that is in-progress."*

Detach sequence (:839-850): `lwkt_serialize_enter` → `wb_stop()` (cancels
pending timer, does NOT wait) → `bus_teardown_intr` →
`lwkt_serialize_exit` → `ether_ifdetach` → `device_delete_child(miibus)` →
`bus_generic_detach` → resources released → return (softc freed by newbus).

## Race

1. wb_tick fires on CPU1 and blocks in `lwkt_serialize_enter` (the detach
   path on CPU0 holds the serializer).
2. CPU0: wb_stop → callout_stop (pending cancelled; in-progress callback not
   interlocked) → releases serializer → completes teardown → softc freed.
3. CPU1: wb_tick acquires the serializer, calls `mii_tick()` on the
   `wb_miibus` child being/mostly deleted (:1198), then
   `callout_reset(&sc->wb_stat_timer, ...)` re-arms a callout living in the
   **freed softc**.
4. One second later the callout fires on freed memory: `wb_tick(sc)` reads
   `sc->wb_miibus` out of freed kernel memory and dereferences it — a
   persistent use-after-free (read/write through freed softc every hz),
   crash or worse depending on slab reuse.

## Trigger

Driver detach with the interface up: `kldunload if_wb.ko`, devctl removal,
or cardbus eject on machines with the W89C840F/RL100-ATX NIC. Privileged
unload; hardware-gated. Window is one tick (hz) but the re-arm makes it
persistent once lost.

## Fix

`callout_drain(&sc->wb_stat_timer)` in wb_detach after the serializer is
released (draining while holding the serializer would deadlock against
wb_tick's own serialize_enter, so drain *after* exit) — see fix.diff.
