DF-2923 / verdict.json
{ "finding_id": "DF-2923", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "vq_vptomp() (vfs_quota.c:420-433) validates vp->v_pfsmp only with mountlist_exists(), which takes no reference; callers (kern_ftruncate vfs_syscalls.c:4123-4124, vn_open vfs_vnops.c:325-326) immediately dereference MP->mnt_op->vfs_account — a concurrent dounmount() of the nullfs mount frees the mount without waiting (the racing thread holds an fd on the UNDERLYING fs's vnode, not the null layer's, so no mnt_refs blocks the free) => use-after-free incl. indirect call through freed memory. Race is real by construction; 20,000+ mount_null/umount cycles (~135/s for 150 s) racing an ftruncate loop did not land the ~10 ns window inside the ~7 ms cycle, so not reproduced. Also noted: v_pfsmp dangles forever after the layer unmount; if the freed memory is reused by a new mount, mountlist_exists() returns TRUE for the wrong mount and accounting is silently redirected.", "exploit_chain": "", "evidence": [ "race-run note in README.md — iter 20000 in 150 s, no hit; guest stayed up", "source citations in README.md (vfs_quota.c:420-433, vfs_cache.c:1386-1389, vfs_syscalls.c:1107-1118, mount.h:657)" ], "kernel_refs": [ "sys/kern/vfs_quota.c:426", "sys/kern/vfs_quota.c:428", "sys/kern/vfs_cache.c:1386-1389", "sys/kern/vfs_syscalls.c:4123", "sys/kern/vfs_vnops.c:325", "sys/kern/vfs_syscalls.c:1107-1118" ], "poc_changes": "Harness built as designed (tmpfs source + null view + forked ftruncate child + root mount/umount churn).", "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": 150, "guest_dirty": 0, "build_cmd": "cc -O -o vqpfsrace vqpfsrace.c", "run_cmd": "/tmp/vqpfsrace /tmp/vqn/f /tmp/vqn /tmp/vqs (root-side churn; ftruncate side is unprivileged)", "code_hash": "e53a80f0a2a1e65324e530ce8b3c6c60b337fcbc5bb547dbb66fa34de8552828", "notes": "Window arithmetic in README. Full unprivileged exploitation additionally needs vfs.usermount=1 (or racing an admin umount). Classified likely-not-reproduced rather than false-positive: the code provably holds no reference across the check-use gap.", "recommended_fix": "mount_hold() the pfsmp under the mountlist token after the existence check (mount_drop after use), or otherwise keep v_pfsmp Referenced for the lifetime of the vnode.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "not_testable: race never landed on the baseline kernel within budget, so a patched comparison has nothing to diff against.", "fix_evidence": "README.md result section" } |