# DF-0573 — Verdict: REPRODUCED (root→kernel OOB; DoS)

## Verdict
REPRODUCED as a root-reachable OOB array index in
`sys/net/ipfw3_nat/ip_fw3_nat.c`. Triggering requires a raw IP socket
(`SYSCAP_NONET_RAW`, i.e. root), so this is a **root→kernel OOB / type
confusion hardening gap**, NOT an unprivileged escalation. The bug
itself is real and confirmed; the exploitability ceiling on this guest
is DoS (kernel wedge) on default GENERIC.

## Mechanism (trigger → primitive → effect)
1. Root opens `AF_INET/SOCK_RAW/IPPROTO_RAW` (requires
   `SYSCAP_NONET_ROOT`, gated by `rip_attach` raw_ip.c:473).
2. Root issues `setsockopt(IPPROTO_IP, IP_FW_X, {opcode=IP_FW_NAT_ADD,
   ioc.id=17, count=1, ip=10.0.2.15}, sizeof)`.
3. The setsockopt lands in `rip_ctloutput` → `ip_fw3_sockopt`
   (sys/net/ipfw3/ip_fw3_glue.c:51) → `ip_fw3_ctl_x` (ip_fw3.c:1039) →
   `ip_fw3_ctl` (ip_fw3.c:1054) → `ip_fw3_ctl_nat_sockopt`
   (ip_fw3_nat.c:881) → `ip_fw3_ctl_nat_add` (ip_fw3_nat.c:770).
4. `ip_fw3_ctl_nat_add` does NOT validate `ioc->id` before dispatching
   `nat_add_dispatch` (ip_fw3_nat.c:731).
5. `nat_add_dispatch` indexes `nat_ctx->nats[ioc->id - 1]` at line 745
   — with `id=17`, this is `nats[16]`, one past `nats[NAT_ID_MAX=16]`
   (zero-based indices 0..15).
6. The OOB slot is read first; if it holds non-NULL garbage, the kernel
   enters the `if (nat_ctx->nats[ioc->id - 1] == NULL)` true branch is
   NOT taken — instead the kernel proceeds to treat the garbage as a
   valid `struct cfg_nat *` and may dereference/write its fields
   (`nat->id`, `nat->alias`, RB tree heads).
7. Observed on default GENERIC (#0 baseline): setsockopt returns 0
   (silent OOB write), then within ~10s the kernel begins emitting
   `spin_lock_ex: softclock_handler, indefinite wait` messages as lock
   structures get corrupted; the system wedges.

## Threat model / reachability ceiling
- **Trigger requires root** (raw IP socket). On this guest an
  unprivileged user (uid=1001) cannot reach this path:
  `socket(AF_INET, SOCK_RAW, IPPROTO_RAW)` → EPERM.
- This is a **root→kernel OOB / type confusion**, which is a real
  hardening/correctness bug (kernel input from a privileged userland
  API must be validated), but it is NOT an unpriv→root escalation.
- Realistic impact on this guest: **DoS (kernel wedge)** via OOB
  memory corruption. No memory corruption primitive was developed into
  a priv-esc chain because there is no unpriv trigger path.

## Why no priv-esc chain was developed
This is one of the **valid hard blockers** listed in the procedure: the
write is reachable **only from an already-root context** (raw IP socket
requires `SYSCAP_NONET_RAW`). Root→kernel game-over is by definition;
there is no privilege boundary to cross. The honest report is:
demonstrated DoS via root-triggered OOB; no unpriv escalation path
exists.

## Fix validation (Phase 8)
Built `fix.diff` which adds bounds checks in `ip_fw3_ctl_nat_add` and
`ip_fw3_ctl_nat_del`:
- `sopt_valsize` minimum-size check (defense in depth)
- `id ∈ [1, NAT_ID_MAX]` check (matches userland validation, now
  enforced in kernel)

Rebuilt the single `ipfw3_nat.ko` module with `make` in
`/usr/src/sys/net/ipfw3_nat`, installed to `/boot/kernel/`, rebooted.

- **Baseline (#0 kernel, unpatched module)**: id=17 → setsockopt
  returns 0 (OOB write happens), kernel wedges within ~10s with
  `spin_lock_ex: indefinite wait` spam.
- **Patched (rebuilt ipfw3_nat.ko)**: id=0/17/-1 → setsockopt returns
  EINVAL; id=1..16 → allowed (NAT created correctly via
  `ipfw3 nat N show config` confirms). Guest stays up indefinitely.

## Kernel references
- sys/net/ipfw3_nat/ip_fw3_nat.c:745 — `nats[ioc->id - 1]` write in
  nat_add_dispatch (confirmed unbounded)
- sys/net/ipfw3_nat/ip_fw3_nat.c:764 — second `nats[ioc->id - 1]` write
- sys/net/ipfw3_nat/ip_fw3_nat.c:797 — `nats[msg->id - 1]` read in
  nat_del_dispatch (confirmed unbounded)
- sys/net/ipfw3_nat/ip_fw3_nat.c:712 — `nats[msg->nat_id - 1]` in
  nat_state_add_dispatch (confirmed unbounded, also dereferences nat
  and alias without NULL check)
- sys/net/ipfw3_nat/ip_fw3_nat.c:161 — `nats[nat_id - 1]` read in
  check_nat (confirmed unbounded, only NULL-checked not range-checked)
- sys/net/ipfw3_nat/ip_fw3_nat.h:40 — `NAT_ID_MAX = 16`
- sys/net/ipfw3_nat/ip_fw3_nat.h:139 — `cfg_nat *nats[NAT_ID_MAX]`
- sys/netinet/raw_ip.c:473 — `caps_priv_check(SYSCAP_NONET_RAW)` gate
  on raw IP socket creation (root-only)
- sbin/ipfw3/ipfw3nat.c:104-106 — userland validation that the kernel
  is missing
