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