⬢ DragonFlyBSD Kernel Audit
DF-3406 / verdict.json
← back to finding ↓ download raw
{
  "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"
  ]
}