# DF-3304 VERDICT — REPRODUCED (unprivileged NULL deref → kernel panic)

## Bottom line
An **unprivileged local user** crashes the kernel (Fatal trap 12, NULL
dereference) by sending one UDP datagram, whenever the machine is acting
as a PIM-SM rendezvous-point router with the advanced MFC API enabled and
an RSVP vif registered. Reproduced deterministically on the guest, twice
(same signature), from a clean boot.

## Chain (all citations sys/net/ip_mroute/ip_mroute.c unless noted)
1. `sys/netinet/ip_output.c:1479-1494` — `IP_MULTICAST_VIF` setsockopt
   has **no privilege check** (`legal_vif_num()` only), and is reachable
   from ordinary UDP sockets (`sys/netinet/udp_usrreq.c:268`).
2. `sys/netinet/ip_output.c:407-435` — for a locally-originated multicast
   datagram, with `ip_mrouter` set, `rsvp_on != 0` (else `imo = NULL`),
   and the destination group not joined on the outgoing ifp, ip_output
   calls `ip_mforward(ip, ifp, m, imo)`.
3. `ip_mroute.c:1186-1198` — imo fast path: `vifi = imo->imo_multicast_vif`
   is valid (register vif installed by the daemon), so
   `return ip_mdq(m, ifp, NULL, vifi)` — **rt = NULL**.
4. `ip_mroute.c:1468-1474` — `xmt_vif` is the REGISTER vif →
   `pim_register_send(ip, viftable + xmt_vif, m, rt)` with rt = NULL.
5. `ip_mroute.c:2826-2827` — `if ((mrt_api_config & MRT_MFC_RP) &&
   (rt->mfc_rp.s_addr != INADDR_ANY))` → NULL+0x90 read → panic.
   (And `pim_register_send_rp` would also read `rt->mfc_parent`,
   `ip_mroute.c:2950`.)

## Preconditions (set up by root in the PoC; realistic for a pimd+rsvpd box)
- kernel built with `options MROUTING` + `options PIM` (note: the stock
  `ip_mroute.ko` module is built WITHOUT PIM — `sys/net/ip_mroute/Makefile`
  passes only `-DMROUTING` — so the module path cannot reach this bug;
  a PIM kernel is required, which is exactly what a PIM-SM RP runs)
- mrouter active: `MRT_INIT` + `MRT_ADD_VIF(VIFF_REGISTER)` (pimd)
- `MRT_API_CONFIG(MRT_MFC_RP)` (pimd advanced API)
- `rsvp_on` set by an RSVP socket `IP_RSVP_VIF_ON` (rsvpd)

## Reproduction (guest, PIMTEST kernel #0 built from stock sources)
```
# ./regvifnull            (root; drops to uid 32767 for the trigger)
MRT_INIT ok
MRT_API_CONFIG(MRT_MFC_RP) rc=0 api=0x100
MRT_ADD_VIF(REGISTER) rc=0
IP_RSVP_VIF_ON rc=0
trigger: uid=32767 euid=32767
trigger: IP_MULTICAST_VIF(0) rc=0        <-- no privilege required
trigger: sending UDP to 224.9.9.9 (expect panic)
trigger: sendto rc=7 errno=0 (no panic?) <-- datagram queued to netisr

Fatal trap 12: page fault while in kernel mode
fault virtual address	= 0x90                     <- offsetof(mfc, mfc_rp)
Stopped at      pim_register_send+0x1de:  movl 0x90(%r13),%r10d   <- r13 = rt = NULL
```
Panic is in the netisr context processing the unprivileged child's
datagram. Full log: `run.log` / `panic.txt`.

## Exploitability
Impact ceiling here is denial of service (NULL deref read, panic). No
controllable write. It is nonetheless the only ip_mroute path in this
file reachable by a fully unprivileged user without any packet
injection — one UDP sendto after the daemon configures the box.

## Fix validation (kernel #1, all five fixes applied)
`./regvifnull` on the patched kernel: full run, child status 0x0,
`no panic observed` (fix_run.log). Baseline panics on the first trigger.

## Recommended fix
`pim_register_send()`: take the upcall path when `rt == NULL` (see
fix.diff):
```diff
-	    if ((mrt_api_config & MRT_MFC_RP) &&
+	    if (rt != NULL && (mrt_api_config & MRT_MFC_RP) &&
 		(rt->mfc_rp.s_addr != INADDR_ANY)) {
```
(Defense in depth: also consider a `priv_check` for IP_MULTICAST_VIF, as
FreeBSD documents the option as "used by multicast routing daemons only".)

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