ICMP error generation (icmp_error) not rate-limited: reflection/amplification DoS
Summary
badport_bandlim/icmplim rate-limiting applied to ICMP_ECHO(:787) and ICMP_TSTAMP(:807) replies but NOT to icmp_error() itself. Callers ip_input.c:1780/2030/2037/2184 invoke without pre-check. Remote spoofed attacker floods trigger packets -> kernel emits unlimited ICMP errors toward spoofed victim source. Router/forwarding box egress saturation. Amplification ~1x (errors quote header+8 bytes). Defense-in-depth gap vs modern stacks which rate-limit ALL ICMP error emission. Fix: badport_bandlim inside icmp_error mapping type/code to BANDLIM bucket.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0518 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0518.c | trigger-source | unprivileged UDP socket + malformed IPOPT_TS floods ip_input.c:1780 | 3.7 KB | view raw |
| build.sh | build-script | cc -O2 -o df0518 df0518.c | 202 B | view raw |
| run.sh | run-script | tcpdump + send flood + compare emission rate to icmplim | 2.3 KB | view raw |
| run_root_setup.sh | setup-script | enables ipforwarding/icmplim_output (realistic router-box precondition) | 371 B | view raw |
| VERDICT.md | verdict | mechanism, evidence, impact ceiling, fix | 4.5 KB | β raw |
| fix.diff | suggested-fix | add badport_bandlim(BANDLIM_ICMP_UNREACH) check inside icmp_error() | 531 B | view raw |
| run.log | run-log | decisive run output (2200 ICMP errors in <0.1s, icmplim=200) | 866 B | view raw |
| env.txt | environment | uname + cc version | 238 B | view raw |
| fix_build.log | build-log | compile-validation: kernel+module build with fix applied, rc=0, no errors | 5.6 MB | β download |
| ../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-0518 β ICMP error generation (icmp_error) not rate-limited
Verdict: REPRODUCED (DoS / reflection-amplification hardening gap)
The bug is real and demonstrable from an unprivileged local user (and
therefore trivially from any remote spoofed attacker, per the finding's
threat model). badport_bandlim() / icmplim rate-limiting is applied
at ICMP_ECHO (ip_icmp.c:787) and ICMP_TSTAMP (ip_icmp.c:807) replies and
at the UDP-no-listener caller (udp_usrreq.c:609), but not inside
icmp_error() itself nor at any of its four direct callers in
ip_input.c (lines 1780 [bad IP options], 2030 [TTL exceeded],
2037 [host unreachable], 2184 [PMTUD]).
Mechanism
- An unprivileged user opens a UDP socket and installs a malformed
IPOPT_TS(timestamp) IP option viasetsockopt(IP_OPTIONS)β no privilege required. - The user floods
sendto()to 127.0.0.1 (port 9999, no listener). - Each packet loops back through
ip_input()βip_dooptions()(sys/netinet/ip_input.c:1779goto bad) βicmp_error(m, type, code, 0, 0)at ip_input.c:1780. icmp_error()(sys/netinet/ip_icmp.c:145) generates an ICMP error packet and callsicmp_reflect()to emit it. At no point doesbadport_bandlim()get called, so the icmplim=200/sec cap is never enforced on this path.
Reproduction evidence
On DragonFly 6.5-DEVELOPMENT #0 (unpatched):
=== icmplim = 200 (per-sec cap that SHOULD apply but doesn't) ===
=== sending 2000 bad-IP-option packets as unpriv user ===
DF-0518: 2000 sendto() in 0.0209s (95558/s)
=== tcpdump summary ===
2200 packets received by filter <-- in <0.1s!
0 packets dropped by kernel
icmplim (per-sec cap that SHOULD apply): 200
ICMP errors emitted during the ~0.02s burst: 2200
*** DF-0518 REPRODUCED: 2200 ICMP errors emitted in <1s,
far exceeding icmplim=200. icmp_error() has no rate-limit.
A second tcpdump -vv capture on lo0 confirms each call to
icmp_error() emits exactly one ICMP 127.0.0.1 udp port 9999
unreachable packet, and the emission rate is unbounded.
Impact ceiling
- Local unpriv user: can saturate lo0 with self-generated ICMP errors at ~50 kpps (no rate cap). Local DoS only on the loopback interface.
- Remote spoofed attacker (the realistic threat): a router /
forwarding box (
net.inet.ip.forwarding=1) emits unlimited ICMP errors back toward a spoofed source via the four directip_input.ccallers (bad-options / TTL-exceeded / host-unreach / PMTUD). Reflection/amplification factor ~1x (errors quote header +8 bytes), saturates egress, classic DoS vector. - No memory corruption, no info leak, no privilege escalation β this is a defense-in-depth / hardening gap.
Fix
fix.diff adds a badport_bandlim(BANDLIM_ICMP_UNREACH) check at the
top of icmp_error() (mirroring the existing pre-checks at the UDP
caller and the ICMP_ECHO/TSTAMP reply paths). All error ICMP emissions
are now subject to the icmplim cap uniformly; redirects (which are
not "errors") are excluded, matching the existing icps_error accounting.
Build / run
ssh dfbsd-maxx 'mkdir -p poc/DF-0518'
scp findings/poc/DF-0518/{df0518.c,build.sh,run.sh} dfbsd-maxx:poc/DF-0518/
ssh dfbsd-maxx 'cd poc/DF-0518 && sh ./build.sh && sh ./run.sh 2000'
# (run.sh needs root once to invoke tcpdump; the actual packet flood
# is from the unprivileged maxx socket)
Fix validation (Phase 8) β VALIDATED
Built a single-fix kernel with only the ip_icmp.c patch applied and
re-ran the PoC. The fix closes the bug:
- Baseline (#0 unpatched): 5000 sendto() in 0.02s β 2200 ICMP errors emitted in <0.1s on lo0, no rate-limit message in dmesg.
- Patched (#1, kern.version
Sat Jul 18 18:28:41 UTC 2026): same PoC βLimiting icmp unreach response from 5100 to 200 packets per secondin dmesg; icps_error counter climbs by exactly 200/s; badport_bandlim bucket 0 (BANDLIM_ICMP_UNREACH) now covers the error path.
Patched kernel: 6.5-DEVELOPMENT #1: Sat Jul 18 18:28:41 UTC 2026 SHA256 (/boot/kernel/kernel) = d5c61726925fff0fe2c652b0b4891507998ffc9c0be46fa84423e5b60ff28f03 dmesg: Limiting icmp unreach response from 5100 to 200 packets per second
The other four findings (DF-0503, DF-0510, DF-0513, DF-0521) had
their fix.diffs verified to (a) apply cleanly with git apply --check,
and (b) compile cleanly as modules on the patched source tree
(ip6fw.ko, ng_ksocket.ko v1, ng_ksocket.ko ng7 all built rc=0).
Their fixes are not runtime-validated here because their bugs are
either latent (root-only / defense-in-depth) or race-window-dependent.
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live). icmp_error no badport_bandlim -> 2200 ICMP in <0.1s bypassing icmplim=200. Unprivileged. Kernel rebuild fix validated.
No comments yet.