DF-3194 / verdict.json
{ "finding_id": "DF-3194", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Statically certain, dynamically not reproduced in ~620k barrier-synchronised 12-thread same-filehandle nfs_nget races across three amplified configurations (16k/58k/76k colliding-fh hash-bucket depths, loopback netisr UDP flood, VM pressure via memhogs, host vCPU oversubscription). The defect: both nfs_nget() race-loser paths (nfs_node.c:194 and :336) drop the freshly created never-connected vnode with a bare vx_put(); vx_put() forces VREF_FINALIZE for VNON vnodes, vnode_terminate() then runs VOP_INACTIVE on it and nfs_inactive() dereferences v_data == NULL at nfs_node.c:393 -> guaranteed page-fault panic whenever the loser path executes. The loser path requires a stale bucket view: either a >=~100-200us mid-scan LWKT preemption of a token-holding scanner (lwkt_gettoken holds no critical section; lwkt_switch releases all tokens, lwkt_thread.c:636) while another CPU completes a full nfs_nget creation, or a >=~2ms block of the lockmgr winner between lockmgr acquire and LIST_INSERT_HEAD while a contender is mid-rescan in the LK_SLEEPFAIL retry loop. Neither could be forced deterministically from unprivileged userspace in the quiet QEMU guest (objcache magazine hits make creation-phase allocations effectively non-sleeping; per-packet netisr preemptors run for microseconds). Impact if it fires in the wild: local unprivileged kernel panic on any NFS-mounted system.", "exploit_chain": "", "evidence": [ "VERDICT.md: full scheduling analysis with the two reachable interleavings, the p > c + tau_c / H >= D conditions, and the campaign table", "run.log: console excerpts of all three hammer runs (each finished 18000-40000 rounds x 12 threads without a panic; guest stayed up)", "fakesrv_race.c: NFSv3 server precomputing 100000 file handles with fnv1_32(fh,32)&0x1FFFF==0 (guest mask; hashinit of vfs_inodehashsize() with kern.maxvnodes=109306 -> hsize 131072) and mapping names [a-m]<idx> to pool[idx] so 12 threads race nfs_nget for one fh without namecache serialization", "race_trigger.c: fill phase + pthread_barrier hammer", "fix.diff: connect (np,vp) before vx_put in both loser paths (plus DF-3196 hunk)" ], "kernel_refs": [ "sys/vfs/nfs/nfs_node.c:189-202", "sys/vfs/nfs/nfs_node.c:194", "sys/vfs/nfs/nfs_node.c:331-350", "sys/vfs/nfs/nfs_node.c:336", "sys/vfs/nfs/nfs_node.c:389-393", "sys/kern/vfs_lock.c:1213", "sys/kern/vfs_lock.c:501-509", "sys/kern/lwkt_thread.c:636-638" ], "poc_changes": "n/a (purpose-built harness: colliding-fh pool with on-disk cache /tmp/fh_pool.bin, per-thread name aliases to defeat namecache-resolution serialization, fill+hammer driver, udp_flood/memhog amplifiers).", "attempts": 3, "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": 2400, "guest_dirty": 0, "build_cmd": "cd /root/df3194 && sh build.sh", "run_cmd": "sh run3.sh 24000 76000 (server + mount + amplifiers + fill + hammer; see run.sh / run3.sh)", "code_hash": "sha256(race_trigger.c)=fd11518bb758cf013bad10aae3148120f323af92979f266345d930214410c034 sha256(fakesrv_race.c)=7573d0c4ab04652d0621b301bbfdca61e2ec9d113e882e57de7de82335e1f274", "notes": "The static chain was verified end-to-end in source (allocvnode sets v_type=VNON; getnewvnode leaves v_data NULL with v_mount set and NFS v_ops; vx_put forces VREF_FINALIZE for VNON; vnode_terminate runs VOP_INACTIVE when v_mount != NULL; vop_inactive dispatch is a plain DO_OPS; nfs_inactive derefs VTONFS(vp) at :393 with no NULL guard). Retest candidates on noisier hardware: interrupt-bursty systems or VM-host oversubscription are the most likely natural triggers.", "recommended_fix": "In both loser paths, connect np<->vp (np->n_vnode = vp; vp->v_data = np) before vx_put() so the forced VOP_INACTIVE/VOP_RECLAIM teardown sees a consistent pair and nfs_reclaim frees np (n_hash.le_prev == NULL); see fix.diff.", "fix_status": "inconclusive", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 8 08:54:41 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff compiles and boots (included in the DF-3195 validation kernel build, RC=0; NFS mount/lookup/open/unlink lifecycle exercised clean on it - see DF-3195 run.fixed.log), but a behavioral before/after comparison is impossible because the baseline race could not be triggered in-guest.", "fix_evidence": "fix.diff hunks 1-2 (nfs_nget :191/:280 loser paths); DF-3195 pack run.fixed.log ran on the kernel containing this fix." } |