DF-3406 / verdict.json
{ "finding_id": "DF-3406", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "tapwrite() leaks exactly one m_get mbuf per EFAULTing write(): the copy loop MGETs the next chain mbuf before re-checking the loop condition, so a part-way uiomove fault (uio_resid still > 0) exits with one unlinked spare mbuf that the error path never frees (if_tap.c:955-969). Reproduced on the stock INVARIANTS kernel: 100,000 faulting writes grew the mbuf pool from 7 to 100,007 (100,263 data mbufs, 50.5 MB = 36% of mb_map), perfectly linear (+1/write), permanent until reboot; ~293k more writes would exhaust mb_map and stall all networking. Fixed by adding m_freem(m) on the error path (fix.diff); on the patched kernel the same 100,000-write storm leaves the count at exactly 7. Privilege bar: requires an open /dev/tapN fd (root:wheel 0600 + tapuopen=0 SYSCAP gate), so Low severity -- privileged local kernel-memory exhaustion.", "exploit_chain": "", "evidence": [ "run.log: baseline netstat -m 7 mbufs -> 100007 after 100000 EFAULT writes (ok=0 efault=100000), 100263 data mbufs, 50515 Kbytes 36% of mb_map; +1 write -> 100008 (linear); count persists over idle", "fix_validation.log: patched kernel #1, same storm, mbuf count stays 7/146632 before and after" ], "kernel_refs": [ "sys/net/tap/if_tap.c:955", "sys/net/tap/if_tap.c:957", "sys/net/tap/if_tap.c:961", "sys/net/tap/if_tap.c:965", "sys/net/tap/if_tap.c:968" ], "poc_changes": "PoC written fresh; unit changed tap0->tap5 after the DF-3405 race wedged unit 0 on the patched kernel (same code path).", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); fix kernel #1: Sat Sep 12 06:59:23 UTC 2026", "runtime_sec": 900, "guest_dirty": 0, "build_cmd": "cc -O2 -o /root/tapleak /tmp/tapleak.c (in-guest)", "run_cmd": "netstat -m | head -3; /root/tapleak 100000; netstat -m | head -3", "code_hash": "bd1d327ee9a0aca90ebcd8a61cfad3e6d416e98c4ca81fa92c2883ca2eb6610e", "notes": "No memory corruption -- pure unbounded kernel memory leak; the leaked objects are counted mbufs so exhaustion also wedges the whole network stack (mb_map denial of service).", "recommended_fix": "Free the spare mbuf on the error path: add m_freem(m) next to m_freem(top) in tapwrite()s error branch (see fix.diff).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Sep 12 06:59:23 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff adds m_freem(m) for the spare mbuf on tapwrite()s error path. Validated in the same patched kernel #1: the identical 100,000-EFAULT-write storm leaves netstat -m at exactly 7/146632 mbufs before and after (baseline stock kernel: 7 -> 100,007 = +1 leaked mbuf per failed write, 36% of mb_map, permanent). Zero growth post-fix; no functional regression.", "fix_evidence": [ "findings/poc/DF-3406/fix_validation.log", "findings/poc/DF-3406/fix.diff" ] } |