# DF-3398 VERDICT

**Status: reproduced** (heap OOB write, cross-object corruption of a
neighbouring kernel allocation; no panic on this guest).

## Root cause (line-accurate)

* `sys/net/ipfw3_nat/ip_fw3_nat.c:310-311`:
  `s->alias_port = htons(s->src_addr * s->dst_addr % ALIAS_RANGE);`
  — src/dst are host-order u32 (`ip_fw3.c:413-414` ntohl's into f_id);
  the product is computed in 32-bit wrapping C arithmetic.
* `sys/net/ipfw3_nat/ip_fw3_nat.c:326`:
  `alias->icmp_in[s->alias_port] = s2;` — raw (byte-swapped on LE) index,
  no ALIAS_BEGIN offset, no bounds check.
* `sys/net/ipfw3_nat/ip_fw3_nat.h:133`: `icmp_in[ALIAS_RANGE=64511]` is the
  final member of `struct cfg_alias` (24 B header: LIST_ENTRY next+le_prev,
  in_addr ip + pad; then tcp_in, udp_in, icmp_in) — total 1,548,288 B.

Index math: x ∈ [0,64510] = wrapped residue; raw index = bswap16(x); OOB
iff x's low byte ∈ {0xFC..0xFF} (1012 residues ≈ 1.57%); write lands at
8..8184 B past the allocation end. Attacker-steerable: choosing src/dst
chooses x (PoC brute-forces dst; the 32-bit wrap keeps every residue
reachable).

## How it was proven (6-vCPU guest, stock INVARIANTS kernel #0)

1. Rebuilt the stock module from pristine source; configured
   `ipfw3 nat 1 config ip 10.0.2.15` + `nat 1 icmp out via vtnet0`.
2. Triggered with x = 0x00FC (raw index 64512). The stack processed the
   flow on cpu0 (raw-IP dispatch); resolved cpu0's cfg_alias chain through
   /dev/kmem (`ip_fw3_nat_ctx` runtime symbol of the loaded module).
3. **Baseline**: qword at alias_end+16 = `0xfffff8004f1025c0` — an
   M_IPFW3_NAT heap pointer — where the *neighbouring* cfg_alias's `ip`
   field (10.0.2.15 = 0x0f02000a, intact on every other cpu at the same
   offset) used to be. That is the `nat_state2*` written by
   `icmp_in[64512]`, 16 bytes past the allocation, corrupting a second
   kernel object. (run.log)
4. **Diagnostic instrumented module** (kprintf only): `rawport=64514`,
   `rawport=64515` for x=0x2fc/0x3fc — the raw index the stock store uses
   is exactly the byte-swapped residue. (run.log)
5. **Fix validation** (`fix.diff`: store at the host-order residue
   `icmp_in[x]`, bounds-check + ntohs the inbound lookup): identical
   trigger on the fixed module leaves the neighbour `ip` intact at +16 on
   all cpus and puts the state in-bounds — `icmp_in[252] =
   0xfffff8004f1025e0` read immediately after the trigger. (fix_run.log)
6. ICMP NAT still translates after the fix (tcpdump showed the full
   outbound/request/reply/forwarded-translation quad with
   net.inet.ip.forwarding=1) — no functional regression.

## Course corrections during verification (honesty notes)

* The first-generation oracle ("third packet" translations in tcpdump) was
  **invalid**: my initial arithmetic ignored the mod-2^32 wrap of the
  product, so the flows I sent had residues like 36656 whose byte-swapped
  indices are in-bounds; those translations exercised normal NAT plumbing,
  not the OOB. The kmem differential (steps 3-5 above) is the actual proof.
* The trigger's cpu pinning is irrelevant — DFly dispatches raw-IP output
  to the flow's hash cpu (cpu0 here); the OOB store follows the flow.

## Impact

8-byte kernel heap pointer written at an attacker-chosen 8-byte-aligned
offset in an 8 KB window behind a 1.5 MB kmem allocation, repeatable at
will (new icmp_id ⇒ new state ⇒ new store), value = fresh `nat_state2`
allocation pointer. Demonstrated consequence: silent corruption of a
neighbouring `cfg_alias` (its alias `ip` — a NAT-correctness field —
clobbered). Depending on allocator state the neighbour is any kmem object;
spraying the ~1012 residues smears pointers across an 8 KB band. Root must
configure the NAT once; afterwards every internal host / unprivileged
local raw-socket user can drive it. Realistic ceiling on this guest:
memory corruption + DoS; a uid=0 chain was not pursued for this
NAT-only-reachable subsystem (the translation engine itself is
root-configured, which lowers privesc value vs. an open local primitive).

## kernel_refs

* sys/net/ipfw3_nat/ip_fw3_nat.c:310 (htons'd wrapped residue)
* sys/net/ipfw3_nat/ip_fw3_nat.c:326 (raw index OOB store)
* sys/net/ipfw3_nat/ip_fw3_nat.h:133 (icmp_in[64511], last member)
* sys/net/ipfw3/ip_fw3.c:413-414 (f_id addrs are host-order)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
