DF-3361 / verdict.json
{ "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)." } |