# DF-3401 — ip_fw3_ctl_nat_get_record walks every CPU's lockless per-CPU NAT state with no serialization

## What

`sys/net/ipfw3_nat/ip_fw3_nat.c:585-694`
(`ip_fw3_ctl_nat_get_record`): after dumping cpu0's ICMP states, the
function iterates

```c
for (cpu = 0; cpu < ncpus; cpu++) {
        nat_ctx = ip_fw3_nat_ctx[cpu];
        ... RB_FOREACH(s, state_tree, &the->rb_tcp_out) {...}
        ... for (i = 0; i < ALIAS_RANGE; i++) { s2 = a1->tcp_in[i]; ... }
}
```

from the calling thread (netisr0), touching the **per-CPU, deliberately
lockless** RB trees and alias pointer arrays of all *other* netisr
CPUs, which their owning netisr threads mutate concurrently
(`ip_fw3_nat` inserts/removes on packet processing;
`nat_cleanup_func_dispatch` frees expired entries on every CPU). No
token, no netmsg dispatch to the owning CPU, nothing. The file's own
header comment (`:96-102`) documents the lockless per-CPU design that
this routine violates.

Consequences while `ipfw3 nat show state` races live traffic:

* RB traversal of a tree being rebalanced/rotated → garbage `s`
  pointers → `s->src_addr`... reads of freed/foreign memory
  (stale-heap contents copied to the root caller as "state records"),
  or a walk that never terminates;
* `tcp_in[i]`/`udp_in[i]` read racing the cleanup callout's
  `a1->tcp_in[i] = NULL; kfree(s2)` (`:969-991`) → use-after-free read
  of a just-freed `nat_state2`.

Also, `:534` uses `break` instead of `continue` when
`nat_ctx->nats[n] == NULL`, so with `ioc_nat_id == 0` (show all) the
ICMP loop stops at the first unused NAT id — functional, but evidence
this path was never carefully reviewed.

## Impact

Requires root to issue IP_FW_NAT_GET_RECORD while NAT traffic flows
(6-CPU window). Worst case: kernel panic (malformed traversal) or
limited stale-heap disclosure to the already-root caller. Low /
likely. Not verified on the guest (Low → Phase V skipped per
contract).

## Fix

Dispatch the per-CPU snapshot collection to each owning CPU via a
netmsg (the file's own `netisr_forwardmsg_all` pattern), or take the
per-CPU state under a lock shared with the data path.
