DF-2993 / verdict.json
{ "finding_id": "DF-2993", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "A single NFSv3 READDIR (or READDIRPLUS) with count=0 from any export-allowed client drives nfsrv_readdir into an infinite kernel loop (the eof short-cut requires uio_resid>0 and vop_write_dirent returns success-with-zero-cookies on resid 0), permanently pinning an nfsd thread holding slp->ns_token; reproduced twice on the stock INVARIANTS guest \u2014 after the trigger the console writer starves, ssh flaps and dies, and in run 1 the whole guest became unresponsive with no panic signature (hard livelock).", "exploit_chain": "mountd/portmap probes to obtain the 32-byte dir fh -> one ~120-byte UDP READDIR(count=0) per nfsd thread -> all server threads spin forever in kernel mode -> NFS socket service dead + host userland starvation.", "evidence": [ "run.log \u2014 two runs: control READDIR(count=8192) 'reply OK', trigger READDIR(count=0) no reply; ssh rc=124/255 progression; vm.sh status down (run 1)", "serial console in run 2: 'NULL: reply 24 bytes in 0.0001s' then '--- TRIGGER readdir count=0 ---' then total silence", "VERDICT.md \u2014 full line-accurate root-cause chain (nfs_serv.c:2977-2984, 3066-3095, 3120-3124; vfs_subr.c:2566-2568; ufs_vnops.c:1612-1636)", "run.fixed.log \u2014 patched kernel returns an immediate error reply for count=0 and stays responsive" ], "kernel_refs": [ "sys/vfs/nfs/nfs_serv.c:2977", "sys/vfs/nfs/nfs_serv.c:3066", "sys/vfs/nfs/nfs_serv.c:3120", "sys/vfs/nfs/nfs_serv.c:3277", "sys/vfs/nfs/nfs_serv.c:3407", "sys/kern/vfs_subr.c:2566", "sys/vfs/ufs/ufs_vnops.c:1612" ], "poc_changes": "wrote nfspoc.c from scratch (raw SUNRPC/UDP client: rpcbind GETPORT, mountd MNT, NFS v3 NULL/READDIR/LOOKUP/WRITE); no seed existed", "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", "runtime_sec": 150, "guest_dirty": 0, "build_cmd": "cc -O -o /root/nfspoc /root/nfspoc.c", "run_cmd": "NFSPOC_SRCPORT=821 NFSPOC_TIMEOUT=5 /root/nfspoc readdir <fh-hex> 0 1", "code_hash": "7faf5661fb7604b4672cb2d79210c692f0f3611e6a0159d6083684f27cee7f2c", "notes": "Guest left clean (vm.sh reset with-src after verification). nfs_privport=1 on this guest: client bound a reserved source port, matching a remote attacker who is root on their own host. The nfsd spin holds slp->ns_token, so one request also kills all further service on that socket.", "recommended_fix": "Reject count/dircount == 0 (siz <= 0) with NFSERR_TOOSMALL in nfsrv_readdir and nfsrv_readdirplus before the retry loop (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 returns an immediate NFSERR_TOOSMALL reply for READDIR count=0 and stays fully responsive; normal READDIR unchanged. Bad behavior gone.", "fix_evidence": [ "run.fixed.log" ] } |