# DF-3436 — ipfw3_basic module unload races its own netmsg consumers (cleanup callout / table radix walk) — NULL-context deref panic

`ip_fw3_state_fini()` (`sys/net/ipfw3_basic/ip_fw3_state.c:683-692`) tears
down per-CPU state contexts via `netisr_domsg` and then does a bare
`callout_stop()` on a handler (`ip_fw3_state_cleanup`,
:584-595) that **unconditionally re-arms itself with `callout_reset`** —
`callout_stop` neither waits for a running handler nor cancels a re-arm
that lands after it. `ip_fw3_state_cleanup_dispatch` (:539-582) also
dereferences `fw3_state_ctx[mycpuid]` with no NULL guard, although
`ip_fw3_state_fini_dispatch` (:676-677) sets it to NULL — a cleanup netmsg
queued behind a fini netmsg on any CPU lands on a NULL context. The
module's MOD_UNLOAD path (`ip_fw3_basic.c:651-657`) has **no in-use guard
at all** — it tears the module down while its netmsg consumers and the
1-second callout are live (and while firewall rules still reference it).

## Reproduce (root, stock kernel #0)

`bounce_basic.sh`: loop `kldunload ipfw3_basic; kldload ipfw3_basic` —
no other activity required (the session had ipfw3 rules installed and a
quiet tap; the "oops" lines in panic.txt are dmesg backlog from the
DF-3434 runs).

## Observed (< 90 s, < 4000 bounce cycles)

```
Fatal trap 12: page fault while in kernel mode
cpuid = 0; lapic id = 0
fault virtual address = 0x28
fault code = supervisor read data, page not present
current process = Idle          <- netisr0
kernel: type 12 trap, code=0
Stopped at rn_flush+0x1d: movq 0x28(%rbx),%rax
```

`rn_flush` (radix-node flush, from the module's teardown/table netmsg
family) walks a NULL/freed tree head on netisr0 — a teardown-vs-consumer
race in exactly the unsynchronized unload path this finding describes.
(`rbx == NULL` → read at 0x28.)

Fix (`fix.diff`): `callout_terminate()` (wait for the self-re-arming
handler) + NULL-guard in `ip_fw3_state_cleanup_dispatch` + the module
should refuse MOD_UNLOAD while the firewall is enabled (guard left as
upstream discussion; the two concrete races are fixed).
