# 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.
