# DF-3370 — ip_output(): NULL rtentry dereference when DF-set packet exceeds MTU on a route-less path (kernel panic)

**Target:** `sys/netinet/ip_output.c:712-715` — the DF/EMSGSIZE path dereferences
`ro->ro_rt` without a NULL check:

```c
	if (ip->ip_off & htons(IP_DF)) {
		error = EMSGSIZE;
		...
		if ((ro->ro_rt->rt_flags & (RTF_UP | RTF_HOST)) &&   /* :712 — NULL! */
```

## Reachable without a route

Two paths reach `sendit`→DF branch with `ro->ro_rt == NULL`:

1. **Multicast + `IP_MULTICAST_IF`** (ip_output.c:309-317): `ifp` comes from
   `imo->imo_multicast_ifp` and the route lookup is skipped entirely.
2. `IP_ROUTETOIF` (ip_output.c:299-308), kernel-internal.

DragonFly has no `IP_DONTFRAG` (sys/netinet/in.h:405 — commented out) and raw
sockets are privileged (`SYSCAP_NONET_RAW`, raw_ip.c:473), so the DF bit comes
from a raw `IP_HDRINCL` socket (root) or TCP (which always has a route).
Trigger = root: raw socket + `IP_HDRINCL` + `IP_MULTICAST_IF`(vtnet0) +
2000-byte packet with `IP_DF` to 224.0.0.5 → multicast branch (ro_rt NULL,
kernel #0) → `ip_len > if_mtu` + DF → **page fault at NULL+0x70**.

Note the ip6 twin guards this (`ip6_output.c:1059-1064` checks
`ro_pmtu->ro_rt == NULL`); the v4 code never got the check.

## Build / run (guest, root)

```
cd /root/rawdf && cc -O -Wall -o rawdf rawdf.c && ./rawdf
```

* stock kernel #0: `Fatal trap 12 ... fault virtual address = 0x70 ...
  Stopped at ip_output+0x5ee: testb $0x5,0x70(%rax)` (see `panic.txt`)
  — 0x70 = offsetof(struct rtentry, rt_flags).
* kernel with `fix.diff` (NULL guard, rebuilt in-guest, kernel #2):
  `sendto returned -1 errno=40 (Message too long)` — EMSGSIZE, no panic
  (see `run.fixed.log`).

Severity is Low only because the trigger is privileged; the missing-NULL-check
itself is deterministic and would also fire for any future unprivileged DF-setter.
