# DF-0573 — ipfw3_nat ioc->id unvalidated array index (OOB R/W)

## Bug
`ioc->id` from the privileged `IP_FW_NAT_ADD`/`IP_FW_NAT_DEL` sockopts
is used directly as `nats[id-1]` in:
- `nat_add_dispatch` (ip_fw3_nat.c:745, :764)
- `nat_del_dispatch` (ip_fw3_nat.c:797, :847)
- `nat_state_add_dispatch` (ip_fw3_nat.c:712)
- `check_nat` (ip_fw3_nat.c:161)

with NO validation that `id ∈ [1, NAT_ID_MAX=16]`. The userland ipfw3
tool validates this (sbin/ipfw3/ipfw3nat.c:104-106), but the kernel
trusts the value. The array `nats[]` is `cfg_nat *nats[NAT_ID_MAX]`
(ip_fw3_nat.h:139), so:
- `id=0` → `nats[-1]` — read/write before the array (whatever struct
  field precedes it in `ip_fw3_nat_context`)
- `id>16` → `nats[16+]` — read/write past the array end

The subsequent `if (nat == NULL)` check at line 162/745 does NOT catch
the OOB — if the OOB slot happens to hold non-NULL (which is true for
slots just past the end where adjacent struct fields live), the kernel
treats random memory as a `struct cfg_nat *`, dereferences it, and
writes to its fields → type confusion → silent memory corruption.

## Setup
Same as DF-0571: ipfw3 modules loaded with
`net.filters_default_to_accept=1`.

## Reproduce
The trigger bypasses the userland ipfw3 validation by calling
`setsockopt(IPPROTO_IP, IP_FW_X, ...)` directly with an embedded
`IP_FW_NAT_ADD` opcode and `ioc.id=17` (or 0).

```
./build.sh
./run.sh        # as root; sends id=17
```

On default GENERIC (#0 baseline): `setsockopt` returns success (the OOB
write happens silently), then the kernel wedges within seconds
(callout/softclock spinlock indefinite-wait messages, then full hang).

## Trigger
- The setsockopt path requires `SYSCAP_NONET_RAW` (raw IP socket). So
  the trigger is **root-only**. This is a root→kernel OOB R/W
  hardening gap, NOT an unpriv→root escalation.
- An unprivileged user CANNOT trigger this: raw IP socket creation
  requires `SYSCAP_NONET_RAW` (verified: `socket(AF_INET, SOCK_RAW,
  IPPROTO_RAW)` → EPERM as uid=1001 on this guest).

## Fix
Add bounds checks in `ip_fw3_ctl_nat_add` and `ip_fw3_ctl_nat_del`
(the sockopt entry points) rejecting `id` outside `[1, NAT_ID_MAX]`.
See fix.diff. Validates correctly: id=0/17/-1 → EINVAL; id=1..16 →
allowed (matches userland behavior, but enforced in kernel).
