⬢ DragonFlyBSD Kernel Audit
DF-3361 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3361",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "dos",
 "confidence": "certain",
 "verdict": "Reproduced. With a plain default scrub rule (scrub in on lo0 all fragment reassemble), 40,000 first-fragments with distinct ip_ids left 6,320 concurrent struct pf_fragment (intended cap PFFRAG_FRAG_HIWAT=1000) and 6,320 pf_frent (intended cap PFFRAG_FRENT_HIWAT=5000, registered in pf_pool_limits[] and tunable via pfctl set limit frags) held with zero enforcement and 0 requests for memory denied \u2014 the limit is dead code. Two further 30k bursts at 46k pps pinned every packet-header mbuf/cluster on the machine (Warning: objcache(mbuf pkthdr+cl) exhausted on cpu1!), after which the guest stopped answering the network entirely (ssh dead, console silent) and had to be hard-reset: system-wide memory-exhaustion DoS from an unauthenticated packet stream. The 30s PFTM_FRAG expiry does not bound concurrent load; each new fragment of a key also refreshes fr_timeout (pf_norm.c:299) so actively-fed sets never expire.",
 "exploit_chain": "",
 "evidence": [
  "findings/poc/DF-3361/run.log (baseline 1/1 -> 6.32K/6.32K after 40k frags; objcache exhausted; guest down)",
  "findings/poc/DF-3361/fragflood.c (raw-socket injector, distinct ip_id keys)",
  "sys/net/pf/pf_norm.c:118-123 (pool_sethardlimit XXXd out), :355-363, :966-971 (unchecked kmalloc)",
  "sys/net/pf/pf.c:281 + sys/net/pf/pf_ioctl.c:240-241 (limits registered, never consulted)"
 ],
 "kernel_refs": [
  "sys/net/pf/pf_norm.c:118-123",
  "sys/net/pf/pf_norm.c:355-363",
  "sys/net/pf/pf_norm.c:966-971",
  "sys/net/pf/pf_norm.c:299",
  "sys/net/pf/pf.c:281"
 ],
 "poc_changes": "Standalone injector written for this pack; raw-socket constraints handled (local source address required, ip_len network order); key space varied via ip_id (remote attackers additionally have the 32-bit source space).",
 "attempts": 2,
 "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64 (X86_64_GENERIC, INVARIANTS)",
 "runtime_sec": 420,
 "guest_dirty": 0,
 "build_cmd": "cc -O2 -o fragflood fragflood.c",
 "run_cmd": "pfctl -f reass.conf && pfctl -e && vmstat -m | grep pffr && /root/poc/fragflood 40000 1024 && vmstat -m | grep pffr && netstat -m",
 "code_hash": "80c2dd6bdf148a3bbe7c5ea504f99c07b0fc923828acc3f67308cc2e6848ebde",
 "notes": "Scrub reassemble is the DEFAULT scrub mode, so this affects the most common pf.conf idiom (scrub in all). Upstream pf enforces the 5000-frent cap via pool hard limits; the DragonFly port disabled them. Guest was wedged (not panicked) at peak; hard-reset via vm.sh reset with-src afterwards.",
 "recommended_fix": "Enforce pf_pool_limits[PF_LIMIT_FRAGS].limit (and PFFRAG_FRCACHE_HIWAT for the cache path) before allocating: flush first, then drop with PFRES_MEMORY if still above the cap (see fix.diff)."
}