# DF-0418 — Unbounded default-router/prefix list growth from RA flood

## Verdict: REPRODUCED (DoS) + FIX VALIDATED

## Summary
`defrtrlist_update()` (sys/netinet6/nd6_rtr.c:689) kmallocs a fresh
`struct nd_defrouter` for every unique RA source link-local address and
`TAILQ_INSERT_TAIL`s it with **no upper bound**. `nd6_prelist_add()`
(nd6_rtr.c:765) does the same per unique prefix, and `pfxrtr_add()` (:727) per
prefix-router pair. The only cap in the tree, `nd6_maxndopt` (nd6.c:128),
limits the number of ND options **per packet**, not cross-packet accumulation.
An on-link unauthenticated attacker who floods RAs with a fresh randomized
source + prefix each causes unbounded `M_IP6NDP` kmalloc → kernel memory
exhaustion → system hang/panic. This is the classic RFC 6104/6105 RA-flooding
DoS.

## Reachability (the realistic precondition)
`nd6_ra_input()` (nd6_rtr.c:200) accepts an RA **only** when
`!ip6_forwarding && (ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV)` (:221).
`ND6_IFF_ACCEPT_RTADV` is set per-interface at init from the global
`net.inet6.ip6.accept_rtadv` (nd6.c:210), which is **0 by default**. So on a
default DragonFly install RAs are NOT processed; the bug is reachable only on a
host that has enabled SLAAC (`accept_rtadv=1`) — a common, realistic
configuration for any IPv6 autoconfig host. The CVSS AV:A (adjacent) vector is
correct: the attacker must be on-link.

## Reproduction (baseline #0 unpatched kernel)
Forged RAs are injected as **ingress** on a `tap0` interface (writing raw
Ethernet+IPv6+ICMPv6 RA frames to `/dev/tap0` delivers them to the kernel RX
path, exercising `nd6_ra_input` exactly as a real on-link RA would). Each RA
carries a unique `fe80::` source and a unique `2001:db8:<i>::/64` prefix.

```
precondition: net.inet6.ip6.accept_rtadv=1 + ND6_IFF_ACCEPT_RTADV on tap0
before: ndp=6 entries, free pages=833542
flood 2000 RAs (unique src + prefix each)
after:  ndp=4006 entries, free pages=832973 (-~2.3MB for 2k RAs)
        M_IP6NDP slab = 485K current, 390M cumulative, ~tens of thousands of allocs
```
Memory scales linearly with #unique RAs; an attacker trivially exhausts GBs.
At ~4200 cumulative RAs the slab showed 969K current / 390M cumulative and free
pages had dropped ~10MB. This is an unbounded-growth DoS.

## Exploit chain
Not memory corruption (no primitive derivable) — this is a resource-exhaustion
DoS. No `uid=0` chain applies. Realistic impact ceiling: permanent kernel
memory exhaustion / OOM-kill / hang on any SLAAC (`accept_rtadv=1`) host from an
on-link attacker; no privilege gain.

## Recommended fix (validated)
`fix.diff` adds two per-interface caps, `nd6_maxdefrouters` and
`nd6_maxprefixes` (default 16 each — generous for legitimate multi-router/
multi-prefix deployments, RFC 4191), enforced by counting same-`ifp` entries in
`defrtrlist_update` and `nd6_prelist_add` before the kmalloc. Over-cap entries
are refused and logged. The caps could be promoted to sysctls (trivial) but are
plain globals matching the existing `nd6_maxndopt` style.

### Fix validation (single-fix kernel #1, built + booted)
```
baseline #0  : 2000 RAs -> tap0 grew to ~thousands of autoconf addrs,
               M_IP6NDP slab = 485K-969K current, thousands of allocs
patched #1   : 2000 RAs -> tap0 inet6 addrs = EXACTLY 16 (capped),
               M_IP6NDP slab = 6.8K / 68 allocs
```
The default-router/prefix kmalloc growth is bounded by the fix. (The remaining
~2000 neighbor-cache entries from 2000 distinct source MACs are a *separate*
neighbor-discovery-cache resource, not the router/prefix lists this finding
claims — out of scope here.)

## Files
- `raflood.c` — RA-flood PoC: enables ND6_IFF_ACCEPT_RTADV, injects forged RAs via /dev/tap0
- `fix.diff` — per-interface caps (nd6.c globals + nd6_rtr.c checks + nd6.h externs)
- `run.log` — baseline reproduction (ndp 6->4006)
- `fix_build.log` — full single-fix kernel build output (rc=0)
- `fix_run.log` — patched-kernel run (capped at 16)
- `env.txt` — guest environment
