Unbounded default-router and prefix list growth from spoofed RAs: RA-flooding kernel memory exhaustion DoS
Summary
defrtrlist_update(:689-702) kmalloc nd_defrouter per unique RA src linklocal, TAILQ_INSERT no upper bound. nd6_prelist_add(:765-782) kmalloc nd_prefix + pfxrtr_add(:727) per unique prefix per router. No per-router/prefix limit anywhere (nd6_maxndopt caps per-packet opts not cross-packet). On-link unauth attacker sends stream of RAs each with fresh randomized src+prefix -> unbounded kmalloc -> kernel memory exhaustion -> system hang/panic. Classic RA-flooding (RFC 6104/6105). Linux/FreeBSD added caps; DragonFly lacks them.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0418 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| raflood.c | trigger-source | RA-flood PoC: enable accept_rtadv + inject forged RAs via /dev/tap0 | 7.5 KB | view raw |
| fix.diff | suggested-fix | per-interface caps nd6_maxdefrouters/nd6_maxprefixes (nd6.c+nd6_rtr.c+nd6.h) | 2.5 KB | view raw |
| build.sh | build-script | cc -o raflood raflood.c | 148 B | view raw |
| run.sh | run-script | enable accept_rtadv + flood 2000 RAs on tap0 | 487 B | view raw |
| run.log | run-log | baseline #0: ndp 6->4006, M_IP6NDP 485K | 1.0 KB | view raw |
| fix_build.log | build-log | single-fix kernel build rc=0 | 5.6 MB | β download |
| fix_run.log | run-log | patched #1: capped at 16 prefixes, M_IP6NDP 6.8K | 373 B | view raw |
| VERDICT.md | verdict | full mechanism + reproduction + fix validation | 3.9 KB | β raw |
| env.txt | environment | uname | 150 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
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_TAILs 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/tap0fix.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
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live). Unbounded defrouter/prefix growth from RA flood -> M_IP6NDP 485K+ exhaustion. accept_rtadv=1. Fix: cap nd6_maxdefrouters/prefixes=16.
No comments yet.