# DF-3501 — ng_fec_addport arpcom type confusion: OOB write past non-arpcom ifnet allocations + kernel-pointer leak + live-field corruption (panic)

- File: `sys/netgraph/fec/ng_fec.c` (v1 netgraph, LIVE)
- Lines: 378-405 (`ng_fec_addport`), specifically 379 (`ac->ac_netgraph =
  priv->node`), 386-392 (first-member MAC copy), 398-405 (MAC read-back)
- Severity: Medium (kernel heap OOB write + info leak + kernel panic;
  control plane root-only on stock DFly) — memcorrupt bucket

## Root cause

`ng_fec_addport()` looks up ANY interface by name (`ifunit()`) and
unconditionally casts it to `struct arpcom *` with no `if_type`
validation. `struct arpcom` extends `struct ifnet` by 24 bytes
(`ac_enaddr[6]` at +0, `ac_netgraph` at +16 past the ifnet). For any
non-arpcom member this yields:

- an 8-byte write of the ng node pointer at member_ifp + sizeof(ifnet) + 16,
- a 6-byte write of the bundle MAC at member_ifp + sizeof(ifnet) + 0,
- a 6-byte READ of the same location (first-member path) disclosed as the
  fec interface's MAC address (world-readable).

Measured on the guest: `sizeof(struct ifnet)` = 928, `off(ac_enaddr)` = 928,
`off(ac_netgraph)` = 944. lo0's ifnet is a standalone
`kmalloc(sizeof(struct ifnet))` (sys/net/if_loop.c:111) — the writes land
outside the 928-byte allocation. For gif, `struct gif_softc` has `gif_if`
first followed by `gif_psrc`/`gif_pdst` pointers (sys/net/gif/if_gif.h:50-53)
— the writes land on live driver fields.

## Reproduction summary (full detail in run.log)

1. **OOB write proof (lo0, second member)** — dumpif.ko shows lo0's tail
   (bytes past the ifnet allocation) all zeros before, and after
   `add lo0`: vtnet0's MAC `52:54:00:12:34:56` at +928 and the fec node
   pointer `0xfffff8004f1ae2e0` at +944. On this build the slab chunk class
   for 928 bytes is 960, so the writes fall in the 32-byte inter-chunk
   slack; any layout/size change (or a member ifnet without slack) makes
   this adjacent-object corruption.
2. **Kernel-pointer leak (gif1, first member)** — `ifconfig fec1` shows
   `ether f0:2d:0a:4f:00:f8` = low 6 bytes of `gif1->gif_psrc`
   (`0xfffff8004f0a2df0`): a kernel heap pointer read past the ifnet and
   disclosed to unprivileged userspace (reading interface MACs needs no
   privilege).
3. **Live-field corruption → panic (gif0, second member)** — adding an
   unconfigured gif0 writes fec1's MAC bytes over `gif0->gif_psrc` (NULL →
   garbage non-NULL) and the node pointer over `gif_ro[0]`; the first
   `ifconfig gif0` dereferences it: `Fatal trap 9: general protection
   fault … Stopped at gif_ioctl+0x9f` (panic.txt).

## Harness

The stock control plane cannot reach addport on this INVARIANTS build
(DF-3499 then DF-3500 facet 1 panic first), so verification uses the lab
module (HEAD source + documented enablers E1/E2/E3 — none touch the
defect) driven from thread context by fecdrive.ko:

    sh setup_guest.sh                       # apply enablers, build, load
    sysctl -n debug.fecdrive=create
    sysctl -n debug.fecdrive="add vtnet0"   # first member (ether, legal)
    sysctl -n debug.fecdrive="add lo0"      # OOB write past lo0's ifnet
    sysctl -n debug.fecdrive="addg gif1"    # pointer leak via fec1 MAC
    sysctl -n debug.fecdrive="addg gif0"    # gif_psrc corruption
    ifconfig gif0                          # -> kernel GPF panic

## Fix (validated)

Reject non-ethernet members in addport (`bifp->if_type != IFT_ETHER` →
EPROTONOSUPPORT). Validated in-guest: lo0/gif1/gif0 rejected with
"fec0: iface … is not an ethernet interface", dumpif shows lo0's tail
unchanged (all zeros), gifs remain fully functional, vtnet0 membership
still works.
