DragonFlyBSD Kernel Audit
DF-2995 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2995",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "With vfs.nfs.gatherdelay_v3 > 0, every v3 WRITE whose filehandle-length word is neither 0 nor NFSX_V3FH leaks ~2 mbufs permanently in nfsrv_writegather: NEGREPLYOUT's -2 path builds reply #1 via nfsm_reply(), then the local nfsmout unconditionally builds reply #2 via nfsm_writereply(), dropping #1 without m_freem. Measured 8 -> 607 mbufs after 300 requests (~599 leaked, persistent, server otherwise healthy); control with gatherdelay_v3=0 leaked exactly 0.",
  "exploit_chain": "remote mbuf-zone exhaustion: ~100-byte UDP request removes ~2 mbufs forever; at scale every m_getl(M_WAITOK) blocks and all nfsd threads hang",
  "evidence": [
    "run.log \u2014 netstat -m before (8 mbufs) / after 300 malformed WRITEs with gatherdelay_v3=5000 (607 mbufs) / after 300 more with gatherdelay_v3=0 (unchanged 607)",
    "VERDICT.md \u2014 line-accurate chain: nfs_serv.c:1263 NEGREPLYOUT(-2) builds reply #1 (nfsm_subs.c:946-950, nfsm_subs.h:114-122), nfs_serv.c:1306-1319 nfsmout builds reply #2 unconditionally"
  ],
  "kernel_refs": [
    "sys/vfs/nfs/nfs_serv.c:1263",
    "sys/vfs/nfs/nfs_serv.c:1307",
    "sys/vfs/nfs/nfs_serv.c:1311",
    "sys/vfs/nfs/nfsm_subs.c:946",
    "sys/vfs/nfs/nfsm_subs.h:114",
    "sys/vfs/nfs/nfs_syscalls.c:667"
  ],
  "poc_changes": "wrote nfspoc.c from scratch (writebadfh subcommand: v3 WRITE with fhlen=5)",
  "attempts": 1,
  "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",
  "runtime_sec": 90,
  "guest_dirty": 0,
  "build_cmd": "cc -O -o /root/nfspoc /root/nfspoc.c",
  "run_cmd": "sysctl vfs.nfs.gatherdelay_v3=5000 && NFSPOC_SRCPORT=813 /root/nfspoc writebadfh 300 && netstat -m | head -3",
  "code_hash": "7faf5661fb7604b4672cb2d79210c692f0f3611e6a0159d6083684f27cee7f2c",
  "notes": "Gated on gatherdelay_v3 > 0 (stock default 0) \u2014 hence Low severity; knob is RW and used for write-performance tuning. v2 writes cannot trigger it (fixed-length fh).",
  "recommended_fix": "In nfsrv_writegather's nfsmout, only build the EIO reply when info.mreq == NULL, reusing the reply nfsm_reply() already built (fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Fri Sep  4 21:02:10 UTC 2026\troot@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Patched kernel: mbuf usage no longer scales with request count (300/900 requests both plateau at 307; bounded reply-cache retention only) vs the buggy kernel +2/request unbounded. Bad behavior gone.",
  "fix_evidence": [
    "run.fixed.log"
  ]
}