β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0518

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0518 Β· 11 files
FileTypeDescriptionSize
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
VERDICT.md verdict mechanism, evidence, impact ceiling, fix
↓ download 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

  1. An unprivileged user opens a UDP socket and installs a malformed IPOPT_TS (timestamp) IP option via setsockopt(IP_OPTIONS) β€” no privilege required.
  2. The user floods sendto() to 127.0.0.1 (port 9999, no listener).
  3. Each packet loops back through ip_input() β†’ ip_dooptions() (sys/netinet/ip_input.c:1779 goto bad) β†’ icmp_error(m, type, code, 0, 0) at ip_input.c:1780.
  4. icmp_error() (sys/netinet/ip_icmp.c:145) generates an ICMP error packet and calls icmp_reflect() to emit it. At no point does badport_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 direct ip_input.c callers (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 second in 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

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Sat Jul 18 18:28:41 UTC 2026

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.