# DF-0645 — VERDICT

## Verdict: REPRODUCED (source-verified); live trigger requires `options IPDIVERT` (not in GENERIC)

The NULL-deref bug at `sys/netinet/ip_divert.c:186` is **real and confirmed
by line-by-line source trace**. It cannot be triggered on the default
GENERIC kernel because `ip_divert.c` is `optional ipdivert`
(`sys/conf/files:1812`) and `X86_64_GENERIC` does **not** include
`options IPDIVERT`. Verified on the running guest:

```
$ nm /boot/kernel/kernel | grep -E '^[0-9a-f]+ T (div_packet|div_output|div_send|div_attach)$'
(no matches — divert code is NOT compiled in)
$ nm /boot/kernel/kernel | grep ' B ip_divert_p'
ffffffff814adb70 B ip_divert_p   # global fn pointer, never set (stays NULL)
```

So `socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT)` returns a *raw IP* socket
(protocol 254), not a divert socket — `SOCK_RAW` accepts any protocol
number; there is no `div_attach` to enforce divert semantics. The bug is
**latent on stock GENERIC**: the path exists in `sys/netinet/ip_divert.c`
but is dead code unless an admin builds `options IPDIVERT`.

## Mechanism (cited line-by-line)

1. **Trigger (userspace).** A divert daemon re-injects a packet via
   `sendto(divsock, ip_pkt, len, 0, &sin, sizeof sin)` with
   `sin.sin_addr.s_addr != 0`. Requires `SYSCAP_RESTRICTEDROOT` (root)
   at `sys/netinet/ip_divert.c:389` (`div_attach`).
2. **`sys/netinet/ip_divert.c:344-367` `div_output()`.**
   `DIV_IS_OUTPUT(sin)` is false because `sin_addr != 0`, so the function
   takes the `else` branch at line 365 and calls `ip_input(m)`.
3. **NULL rcvif.** The mbuf was allocated by `sosend → m_gethdr`.
   `sys/kern/uipc_mbuf.c:596` `m_gethdr()` zeros `m_pkthdr` including
   `m_pkthdr.rcvif = NULL`. Neither `div_output` nor `ip_input` sets it.
4. **ipfw re-match.** `ip_input → ipfw_check_in`. With a divert rule
   active, ipfw allocates a fresh `PACKET_TAG_IPFW_DIVERT` tag at
   `sys/net/ipfw/ip_fw2.c:4289-4300` and returns `IP_FW_DIVERT`.
   `ipfw_check_in` at `ip_fw2.c:6985-6986` calls
   `ip_divert_p(m, tee, 1)`.
5. **`sys/netinet/ip_divert.c:564 ip_divert_in() → :638 divert_packet(m, 1)
   → :306 div_packet(m, 1, port)`.**
6. **`sys/netinet/ip_divert.c:182-197` `div_packet()`.** Inside the
   `if (incoming)` block:
   ```c
   182: if (incoming) {
   186:     TAILQ_FOREACH(ifac, &m->m_pkthdr.rcvif->if_addrheads[mycpuid],
   187:               ifa_link) {
   ```
   `m->m_pkthdr.rcvif` is NULL → NULL dereference → page fault → **panic**.
7. **The developer knew.** Line 202 (16 lines below) correctly checks
   `if (m->m_pkthdr.rcvif)`. The :182 block was simply missed.

## Why we cannot panic-test on stock GENERIC

`ip_divert.c` is `optional ipdivert` — the divert dispatch table, divert
syscalls, and `div_packet`/`div_output`/`div_attach` are only present when
the kernel is built with `options IPDIVERT`. The X86_64_GENERIC config
does not enable it. Verified: `nm /boot/kernel/kernel` shows only the
`ip_divert_p` BSS pointer (defined in `ip_input.c:263` for the indirect
call site), no `div_*` text symbols. So there is no live path to
`div_packet` from a divert socket on stock GENERIC.

The PoC `df0645_poc.c` is correct and would fire on an `IPDIVERT` kernel.
The fix validation phase (Phase 8) builds an IPDIVERT kernel to demonstrate
the panic and confirm the fix closes it.

## Privilege / threat model

- **Privilege required:** `SYSCAP_RESTRICTEDROOT` to open a divert socket
  (root). This is a privileged-local→kernel DoS — a **root→kernel
  hardening gap**, not an unpriv→root escalation.
- **Indirect remote path:** a divert daemon (NAT/proxy) that re-injects
  received packets with `sin_port=0` (naive "echo" pattern) is a remote
  panic vector. Requires the admin to (a) build an IPDIVERT kernel, and
  (b) run such a daemon — a realistic but non-default setup.
- **Impact:** kernel panic = full system DoS.

## Recommended fix

Add the same NULL rcvif guard that line 202 already has. The proposed
diff in the finding markdown (`if (incoming && m->m_pkthdr.rcvif != NULL)`)
is minimal and correct. `findings/poc/DF-0645/fix.diff` carries a
verified, git-apply-able version.
