# DF-3387 — VERDICT

**Status: reproduced** — deterministic kernel NULL-deref panic, exact predicted
instruction. **Impact: dos (panic)** — write-only fault at fixed vaddr 0xa0,
no controllable primitive. **Fix: validated (fixed)** — patched kernel boots,
same PoC harmlessly discards the frame, guest stays up.

## Root cause (line-accurate)

`sys/net/lagg/if_lagg.c:1462-1471`:

```c
	if (m != NULL) {
		IFNET_STAT_INC(ifp, ipackets, 1);
		IFNET_STAT_INC(ifp, ibytes, m->m_pkthdr.len);

		if (scifp->if_flags & IFF_MONITOR) {
			m_freem(m);
			m = NULL;                       /* <-- m consumed   */
		}
		ether_reinput_oncpu(scifp, m, REINPUT_RUNBPF);   /* <-- NULL passed  */
	}
```

`sys/net/if_ethersubr.c:1303-1321` (`ether_reinput_oncpu`) accepts no NULL —
first deref with the lagg master IFF_UP:

```c
	if ((reinput_flags & REINPUT_KEEPRCVIF) == 0 ||
	    m->m_pkthdr.rcvif == NULL)          /* m == NULL -> fault at 0xa0 */
	{
		m->m_pkthdr.rcvif = ifp;
```

Attack chain (all reproduced on the guest):

1. `ifconfig tap0 create && ifconfig tap0 up`
2. `ifconfig lagg0 create && ifconfig lagg0 laggproto failover &&
    ifconfig lagg0 laggport tap0` — enslaving sets the slave's
    `if_type = IFT_IEEE8023ADLAG` (if_lagg.c:674), which routes its RX to the
    lagg branch of `ether_demux_oncpu` (if_ethersubr.c:1076) — before any L3
    processing, so frame content beyond the 14-byte header is irrelevant.
3. `ifconfig lagg0 up monitor` — master gets `IFF_UP|IFF_MONITOR`.
4. One frame *received* on tap0. `write(2)` to `/dev/tap0` is an RX injection:
   `tapwrite()` (sys/net/tap/if_tap.c) ends in
   `ifp->if_input(ifp, top, NULL, -1)` → `ether_input` →
   `ether_input_handler` → `ether_input_oncpu` → `ether_demux_oncpu` →
   `lagg_input` → failover input returns the mbuf (tap0 is primary) →
   IFF_MONITOR branch NULLs it → `ether_reinput_oncpu(scifp, NULL, ...)`.
5. `Fatal trap 12`, fault vaddr `0xa0`, stopped at
   `ether_reinput_oncpu+0x1a: movq %rdi,0xa0(%rsi)` — `m == NULL (rsi)`,
   the write of `ifp` to `m->m_pkthdr.rcvif`.

Runs: 2/2 identical panics (run.log/run.2.log, panic.txt/panic.2.txt). The
ssh-side `write()` return line is lost both times because the panic freezes
the guest before sshd flushes; the serial-console mirror
(`[df3387] ... up monitor -> rc=0`) pins the sequence — the only action the
program takes after that line is `open("/dev/tap0")` + `write(fd, frame, 34)`.

## Threat model

- Setup is privileged (ifconfig). After setup, the trigger is **any received
  frame** on any slave — ambient broadcast traffic suffices; a remote
  attacker on the L2 segment panics the box with a single packet.
- The faulting instruction writes `ifp` to fixed address `0xa0` — not
  attacker-controlled, page 0 unmapped → clean DoS panic, no escalation path.

## Why pass-1 missed it / where it sits

This is a *callsite* violation of the `ether_reinput_oncpu` contract
documented in the audited file (if_ethersubr.c:1303). The other four reinput
callers (vlan if_vlan.c:630, carp ip_carp.c:1231/1253, bridge
if_bridge.c:2880/2987) all pass non-NULL mbufs — lagg is the outlier.
DF-0367 covers a different bug in lagg_input (unlocked `ifp->if_lagg` deref,
lines 1442-1456); the NULL reinput at 1466-1470 is distinct and was unfiled.

## Fix validation

`fix.diff` (two hunks: primary `return` in if_lagg.c after monitor-discard;
defensive NULL guard at the top of `ether_reinput_oncpu`):

- Baseline (stock #0 kernel): panic as above — reproduced before patching.
- Patched: applied in-guest to `/usr/src`,
  `make -j8 nativekernel KERNCONF=X86_64_GENERIC` + `make installkernel`,
  rebooted into `#1` kernel. Same PoC: all 8 steps rc=0, frame discarded by
  the monitor path, `NO PANIC — bug did not reproduce`, interfaces cleanly
  destroyed, guest stays up (`vm.sh status` = up, ssh alive).
  See run.patched.log + build.log.
- Guest then reset to the clean snapshot (`vm.sh reset with-src`).

## Notes

- `ether_reinput_oncpu`'s only other NULL hazard (`m_freem(NULL)` on a
  !IFF_UP interface) is harmless — `m_freem()` is `while (m)`-guarded
  (sys/kern/uipc_mbuf.c:1469). The deref at :1318-1320 is the crash.
- Could an *unprivileged* user reach this without root setup? No — SIOCSLAGG
  and SIOCSIFFLAGS are privileged; the finding is privileged-config +
  remote/unprivileged-trigger, same profile as DF-0445 (Medium).

## 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.
