vfs_setpublicfs() use-after-vput of root vnode + refcount leak on VFS_VPTOFH error
| Field | Value |
|---|---|
| ID | DF-0008 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H |
| CWE | CWE-416 Use After Free; CWE-401 Missing Release of Memory after Effective Lifetime |
| File | sys/kern/vfs_subr.c |
| Lines | 2255-2290 |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
In vfs_setpublicfs(), the root vnode rvp obtained from VFS_ROOT() (which
returns it referenced and locked) is vput() at vfs_subr.c:2261, dropping
its usecount and lock โ but is then dereferenced again at vfs_subr.c:2269
by vn_get_namelen(rvp) โ VOP_PATHCONF(rvp) when ex_indexfile is set.
After vput, rvp is unlocked and, if VFS_ROOT's was the only reference,
eligible for asynchronous reclamation by the vnlru thread โ a
use-after-free. Even absent a free, VOP_PATHCONF is invoked without the
vnode lock, a definite lock-protocol violation. Separately, the error path at
vfs_subr.c:2258-2259 returns on VFS_VPTOFH failure without vput(rvp),
permanently leaking one usecount (the vnode is never reclaimable).
Root cause
sys/kern/vfs_subr.c:2255-2290:
if ((error = VFS_ROOT(mp, &rvp))) /* rvp referenced + locked */
return (error);
if ((error = VFS_VPTOFH(rvp, &nfs_pub.np_handle.fh_fid)))
return (error); /* :2259 LEAK: no vput(rvp) */
vput(rvp); /* :2261 drops ref + lock */
...
if (argp->ex_indexfile != NULL) {
int namelen;
error = vn_get_namelen(rvp, &namelen); /* :2269 USE AFTER vput */
if (error)
return (error);
...
}
vn_get_namelen() (sys/kern/vfs_subr.c:2552) calls VOP_PATHCONF(rvp),
which requires a live, locked vnode. At :2269 rvp is neither. Two
defects:
- Refcount leak (
:2259): theVFS_VPTOFHerror path returns withoutvput(rvp), leaking the usecountVFS_ROOTadded โ the vnode is pinned indefinitely (vnode leak / DoS). - Use-after-
vput/ use-after-unlock (:2261then:2269):vputdrops the ref and the lock;vn_get_namelenthen touchesrvpunlocked. IfVFS_ROOT's was the only reference,rvpmay have been reclaimed (vgonel/vclean) and reused by the timeVOP_PATHCONFdispatches, causing a use-after-free. At minimum,VOP_PATHCONFraces other vnode operations onv_data.
Threat model & preconditions
- Attacker position: a context that can configure a WebNFS public export
(
mount(2)withMNT_EXPUBLIC). Mount/export is privileged (priv_check), so the trigger is root-only โ this limits direct privilege impact. - Privileges gained or impact: kernel memory-safety defect. A legitimate
admin enabling the feature, or a root context in a setup where
mountis partially delegated (e.g. a confined/jail-like or setuid mount helper), can trip a kernel panic (UAF on a reclaimed/reused vnode) or vnode corruption from the unlockedVOP_PATHCONF. The leak variant is an availability issue (vnode exhaustion). - Required config or capabilities: privilege to mount with
MNT_EXPUBLIC; anindexfileset (for the UAF) or a filesystem whosevptofhcan fail (for the leak). - Reachability:
mount(2)โvfs_setpublicfs()(via the export path).
Proof of concept
PoC source: findings/poc/DF-0008/expub.c
Repeatedly issues mount(2) with MNT_EXPUBLIC and an ex_indexfile to
widen the race window against vnlru reclamation of the just-vput() root
vnode.
Build & run (as root)
cc -o expub findings/poc/DF-0008/expub.c mkdir -p /mnt ./expub /dev/adx0s1a /mnt
Expected output
On a DEBUG/INVARIANTS kernel racing vnlru, a panic from VOP_PATHCONF
on a reclaimed/locked vnode. The VFS_VPTOFH-error variant deterministically
leaks a vnode (vnode-count growth that never decreases across failed mounts).
Impact
Genuine kernel memory-safety defect (UAF + refcount leak + lock-protocol violation), but gated behind mount/export privilege, so rated Low. It is a correctness/robustness fix a maintainer can action immediately; in a delegated-mount threat model it is more severe.
Recommended fix
Keep rvp referenced+locked across vn_get_namelen, and ensure every error
path vputs it exactly once. Sketch (keeps a single vput on success and one
on each error path):
--- a/sys/kern/vfs_subr.c
+++ b/sys/kern/vfs_subr.c
@@ -2255,14 +2255,15 @@
if ((error = VFS_ROOT(mp, &rvp)))
return (error);
if ((error = VFS_VPTOFH(rvp, &nfs_pub.np_handle.fh_fid)))
- return (error);
-
- vput(rvp);
+ goto out;
if (argp->ex_indexfile != NULL) {
int namelen;
- error = vn_get_namelen(rvp, &namelen);
+ error = vn_get_namelen(rvp, &namelen); /* rvp still locked+ref'd */
if (error)
- return (error);
+ goto out;
nfs_pub.np_index = kmalloc(namelen, M_TEMP, M_WAITOK);
error = copyinstr(argp->ex_indexfile, nfs_pub.np_index,
namelen, NULL);
@@ -2286,9 +2287,11 @@
if (error) {
kfree(nfs_pub.np_index, M_TEMP);
- return (error);
+ goto out;
}
}
+ vput(rvp);
nfs_pub.np_mount = mp;
nfs_pub.np_valid = 1;
return (0);
+out:
+ vput(rvp);
+ return (error);
}
References
sys/kern/vfs_subr.c:2255-2290โvfs_setpublicfs(leak + UAF).sys/kern/vfs_subr.c:2552โvn_get_namelenโVOP_PATHCONF(needs locked vp).- CWE-416 Use After Free; CWE-401 Missing Release of Memory after Effective Lifetime.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/vfs_subr.c. - pending Reported to DragonFlyBSD security contact.
PoC verification
Evidence pack
findings/poc/DF-0008 ยท 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| expub.c | trigger-source | root-only UPDATE-mount trigger of vfs_setpublicfs :2261->:2269 UAF path (rewritten: correct struct ufs_args + MNT_UPDATE) | 4.1 KB | view raw |
| VERDICT.md | verdict | full narrative: real+reachable, line-by-line proof, why no panic on non-DEBUG kernel | 8.9 KB | โ raw |
| README.md | readme | human build/run/expected summary | 3.1 KB | โ raw |
| build.sh | build-script | cc -o expub expub.c | 253 B | view raw |
| run.sh | run-script | as root: ./expub /boot N (UPDATE mount w/ MNT_EXPUBLIC+indexfile) | 714 B | view raw |
| build.log | build-log | final successful build, full output | 63 B | view raw |
| run.log | run-log | decisive run (/boot 16): path reached 16/16, no panic, guest up | 423 B | view raw |
| run.stress.log | run-log | stress run (/boot 512) with vnode churn: path reached 512/512, no panic | 383 B | view raw |
| dmesg.txt | dmesg | post-trigger dmesg: no vnode/lock/witness/panic warnings | 597 B | view raw |
| env.txt | environment | uname, cc version, kernel config (non-DEBUG, no INVARIANTS/WITNESS), /boot=ufs | 461 B | view raw |
| fix.diff | suggested-fix | keep rvp locked across vn_get_namelen; vput exactly once per path (git apply --check passes) | 1.4 KB | view raw |
| manifest.json | manifest | this catalog | 2.8 KB | view raw |
DF-0008 โ PoC
expub.c โ root-only trigger of the vfs_setpublicfs() use-after-vput of
the root vnode (and the VFS_VPTOFH-error refcount leak on a filesystem whose
vptofh can fail).
Verdict
Real and reachable, but NOT reproduced as an observable fault on the audited
non-DEBUG GENERIC kernel. The vulnerable sequence
VFS_ROOT โ vput(rvp) โ vn_get_namelen(rvp) (i.e. VOP_PATHCONF on the
already-unlocked/unref'd rvp) ran end-to-end 520/520 times across test
runs with no panic, because (1) the X86_64_GENERIC kernel has no
INVARIANTS/WITNESS to catch the unlocked-VOP_PATHCONF lock-protocol
violation, and (2) the UFS mount-root vnode retains references through the
race window so vnlru does not reclaim it (the actual UAF-free never happens
on this vnode). The deterministic refcount-leak variant needs a filesystem
whose VFS_VPTOFH fails; UFS ffs_vptofh always returns 0, and none of the
export-capable fs types on this guest have a failing vptofh. The bug IS
genuine (master DEV unchanged since 2026-06-22) and the fix applies โ see
VERDICT.md for the full line-by-line proof.
The bug
vfs_setpublicfs() (sys/kern/vfs_subr.c:2255-2295):
if ((error = VFS_ROOT(mp, &rvp))) /* rvp referenced + locked */
return (error);
if ((error = VFS_VPTOFH(rvp, ...)))
return (error); /* :2259 LEAK: no vput(rvp) */
vput(rvp); /* :2261 drops ref AND lock */
...
if (argp->ex_indexfile != NULL) {
int namelen;
error = vn_get_namelen(rvp, &namelen); /* :2269 USE AFTER vput */
vn_get_namelen() โ VOP_PATHCONF(rvp) (vfs_subr.c:2552) runs on a vnode
that has already been vput() (unlocked, usecount decremented). If
VFS_ROOT's was the only reference, rvp is reclaimable by vnlru between
:2261 and :2269 โ use-after-free. At minimum it is a definite vnode
lock-protocol violation.
Reachability
mount(2) (UPDATE mount of an already-mounted ufs fs with fspec=NULL and
export.ex_flags = MNT_EXPUBLIC|MNT_EXPORTED, ex_indexfile set) โ
ffs_mount export path (ffs_vfsops.c:270-273) โ vfs_export โ
vfs_setpublicfs. Mount/export is privilege-gated (root only) โ a genuine
kernel memory-safety defect (Low), not an unprivileged escalation.
Build
./build.sh # cc -o expub expub.c
Run (as root)
./run.sh # defaults: /boot, 16 loops # or: ./run.sh /boot 512
Expected output
On a DEBUG/INVARIANTS kernel racing vnlru: a panic from VOP_PATHCONF
on a reclaimed/unlocked vnode. On the default GENERIC kernel (this guest): the
run completes with vfs_setpublicfs UAF path reached ok=N and no panic
โ the lock-protocol violation races silently. The VFS_VPTOFH-error leak
variant is deterministic but needs a vptofh-failing export fs (not available
on this guest).
Impact
Genuine CWE-416 (UAF) + CWE-401 (refcount leak) + vnode-lock-protocol
violation, gated behind mount/export privilege (root). No panic/leak/uid0
obtained on this non-DEBUG kernel; the finding's value is the real, reachable
code defect and the targeted fix in fix.diff.
DF-0008 โ vfs_setpublicfs() use-after-vput of root vnode + refcount leak
Verdict
NOT REPRODUCED as an observable fault on this kernel โ but the defect is
REAL and REACHABLE, and the vulnerable code path is CONFIRMED to execute on
the audited master DEV kernel. This is not a false positive, not
already-fixed: master DEV (committed 2026-06-22) contains the exact buggy
lines unchanged. The vulnerable sequence VFS_ROOT โ VFS_VPTOFH โ vput(rvp) โ
vn_get_namelen(rvp) ran end-to-end 520/520 times across my test runs with
no panic, because the audited X86_64_GENERIC kernel is a non-DEBUG build
(no INVARIANTS/WITNESS to catch the unlocked-VOP_PATHCONF), and the UFS
mount-root vnode retains references through the race window so vnlru does not
reclaim it. The deterministic refcount-leak variant (:2259) needs a
filesystem whose VFS_VPTOFH fails, and none of the export-capable filesystem
types on this guest (hammer2/devfs/ufs/null/tmpfs) have a failing vptofh. The
fix (fix.diff) is warranted โ the defect is genuine.
Classification: not_reproduced (no panic/leak/uid0/dos observed), impact:
none, confidence: likely. The code-level defect itself is certain; the
absence of a live fault is a property of this kernel configuration, not of the
bug.
The bug โ confirmed line-by-line in master DEV
vfs_setpublicfs() (sys/kern/vfs_subr.c:2255-2295):
2255: if ((error = VFS_ROOT(mp, &rvp)))
2256: return (error);
2257:
2258: if ((error = VFS_VPTOFH(rvp, &nfs_pub.np_handle.fh_fid)))
2259: return (error); /* LEAK: no vput(rvp) */
2260:
2261: vput(rvp); /* drops ref AND lock */
2262:
2266: if (argp->ex_indexfile != NULL) {
2267: int namelen;
2269: error = vn_get_namelen(rvp, &namelen); /* USE AFTER vput */
vn_get_namelen() (sys/kern/vfs_subr.c:2547-2557) unconditionally calls
VOP_PATHCONF(vp, _PC_NAME_MAX, retval):
2552: error = VOP_PATHCONF(vp, _PC_NAME_MAX, retval);
Lock/refcount semantics confirmed against the audited source:
VFS_ROOT(mp, &rvp)โufs_root(sys/vfs/ufs/ufs_vfsops.c:58-68) โVFS_VGETโvget, which returnsrvpreferenced andLK_EXCLUSIVE-locked.vput(vp)(sys/kern/vfs_lock.c:703-706) isvn_unlock(vp); vrele(vp);โ it drops both the lock and the usecount.
So at :2269/:2552, VOP_PATHCONF is dispatched on rvp which is now
unlocked and usecount-decremented. Two defects:
- Refcount leak (
:2259) โ theVFS_VPTOFHerror return does so withoutvput(rvp), leaking the usecountVFS_ROOTadded (vnode pinned indefinitely). Not reachable on UFS (ffs_vptofh,sys/vfs/ufs/ffs_vfsops.c:1228-1239, always returns 0); reachable on any filesystem whosevptofhfails (e.g. one using the defaultvfs_stdvptofhโEOPNOTSUPP,sys/kern/vfs_default.c:1562-1566). - Use-after-
vput/ lock-protocol violation (:2261then:2269) โVOP_PATHCONFruns on an unlocked vnode; ifVFS_ROOT's was the only reference,rvpmay have been reclaimed (vgonel/vclean) byvnlruin the window, yielding a use-after-free in the VOP dispatch.
Reachability โ CONFIRMED on the audited kernel
vfs_setpublicfs() is reachable from userspace via the export path:
mount(2) โ ffs_vfsops.c:179 (copyin ufs_args)
โ ffs_vfsops.c:187 (MNT_UPDATE branch)
โ ffs_vfsops.c:270-273 (fspec==NULL โ vfs_export)
โ vfs_subr.c:2201-2204 (ex_flags & MNT_EXPUBLIC โ vfs_setpublicfs)
โ vfs_subr.c:2255-2295 โ the buggy sequence
The trigger (expub.c) issues an UPDATE mount of /boot (ufs) with
struct ufs_args { .fspec=NULL, .export.ex_flags=MNT_EXPORTED|MNT_EXPUBLIC,
.export.ex_indexfile="index.html" }. Each successful mount proves the entire
buggy path ran (the :2266 indexfile branch is taken because ex_indexfile !=
NULL, so :2269 vn_get_namelen(rvp) executes on the already-vput rvp).
Result on the audited kernel (/root/expub /boot N):
| run | loops | path reached | panic | guest |
|---|---|---|---|---|
| run.log | 16 | 16/16 ok | none | up |
| run.stress | 512 | 512/512 ok | none | up |
(Total 520/520 successful triggering mounts.) The mount returns 0 each time,
so vfs_setpublicfs set nfs_pub.np_valid=1 and mp->mnt_flag |= MNT_EXPUBLIC
โ i.e. the buggy :2261โ:2269 sequence completed end-to-end 520 times.
Why no panic on this kernel (honest assessment)
- No
INVARIANTS/WITNESS.sysctl kern.conftxtshows noINVARIANTS/WITNESS/DEBUG/VFS_DEBUGin theX86_64_GENERICconfig. AWITNESSbuild would flag the unlockedVOP_PATHCONF(vnode-lock protocol violation); anINVARIANTSbuild assertsvn_lockstate in VOP entry and would panic. This kernel has neither, so the violation races silently. - Mount-root vnode is not reclaimed in the window. The
/bootroot vnode is held by the mount structure (mpkeeps a reference to its root), so aftervput(rvp)decrements theVFS_ROOT-added usecount, the vnode's usecount remains โฅ1 andvnlrudoes not reclaim it between:2261and:2269. The lock-protocol violation occurs, but the actual UAF-free never happens on this vnode. Concurrent vnode churn (the stress run) does not change this โvnlruonly reclaims vnodes with usecount 0.
So on a default kernel the UAF-half is a silent correctness/robustness defect;
on a DEBUG/INVARIANTS kernel (or with a sufficiently adversarial vnode
whose only ref was VFS_ROOT's) it panics. The leak-half is deterministic but
needs a vptofh-failing export-capable filesystem, which this guest does not
provide.
Impact
Genuine kernel memory-safety defect (CWE-416 use-after-free +
CWE-401 refcount leak + vnode-lock-protocol violation), but gated behind
mount/export privilege (sys_mount โ caps_priv_check_td for the fs cap,
plus a separate check when MNT_EXPORTED is set, vfs_syscalls.c:164-168).
Direct exploitation requires root (or a delegated-mount / setuid-mount-helper
threat model). Rated Low. No panic/leak/uid0 was obtained on this kernel; the
value of the finding is the real, reachable code defect and the fix.
Exploit chain
None developed โ the primitive is gated behind root and does not produce a
corruption primitive on this non-DEBUG kernel. A root attacker who can trigger
the UAF-free variant (a vptofh-failing export fs, or a DEBUG kernel) would
get a vnode UAF that could in principle be groomed into corruption, but that
is not achievable in the current guest configuration.
PoC changes
Rewrote expub.c substantially. The original passed a bare struct
export_args (384 B) as the mount(2) data argument, but ffs_mount does
copyin(data, &args, sizeof(struct ufs_args)) with struct ufs_args = 448 B
({ char *fspec; struct export_args export; }). The original therefore fed
the kernel misaligned garbage and never reached the export path. The rewrite:
- Uses the correct
struct ufs_args(#include <vfs/ufs/ufsmount.h>),fspec=NULL,.export.ex_flags = MNT_EXPORTED|MNT_EXPUBLIC,.export.ex_indexfile = "index.html". - Issues an UPDATE mount (
MNT_UPDATE) of an already-mounted ufs fs (/boot), which is the only path inffs_mountthat processes export args (ffs_vfsops.c:187,270-273). - Clears the public export with
MNT_DELEXPORTbetween iterations so the singletonnfs_pubcan be re-set (vfs_subr.c:2246). - Loops and reports how many times the buggy path was reached.
Added the repro glue (build.sh, run.sh) and the full evidence logs
(build.log, run.log, run.stress.log, env.txt, manifest.json,
fix.diff).
How to reproduce
./build.sh # as maxx: cc -o expub expub.c # as root: cp expub /root/expub /root/expub /boot 16 # reaches the buggy :2261->:2269 path 16 times
On a DEBUG/INVARIANTS kernel this panics in VOP_PATHCONF on the unlocked
vnode; on a default GENERIC kernel it races silently (the run reports
vfs_setpublicfs UAF path reached ok=N).
References
sys/kern/vfs_subr.c:2255-2295โvfs_setpublicfs(the buggy sequence).sys/kern/vfs_subr.c:2547-2557โvn_get_namelenโVOP_PATHCONF(needs locked vp).sys/kern/vfs_lock.c:703-706โvput=vn_unlock+vrele(drops lock AND ref).sys/vfs/ufs/ufs_vfsops.c:58-68โufs_rootโVFS_VGET(returns ref'd+locked).sys/vfs/ufs/ffs_vfsops.c:1228-1239โffs_vptofhalways returns 0 (leak path unreachable on UFS).sys/kern/vfs_default.c:1562-1566โvfs_stdvptofhreturnsEOPNOTSUPP(leak path for fs w/o vptofh).sys/vfs/ufs/ffs_vfsops.c:187,270-273โ UPDATE-mount export path โvfs_export.sys/kern/vfs_subr.c:2201-2204โvfs_exportโvfs_setpublicfsonMNT_EXPUBLIC.sys/kern/vfs_syscalls.c:164-168โ export requires privilege (root-only).- CWE-416 Use After Free; CWE-401 Missing Release of Memory after Effective Lifetime.
Confirmed kernel references
- sys/kern/vfs_subr.c:2255
- sys/kern/vfs_subr.c:2259
- sys/kern/vfs_subr.c:2261
- sys/kern/vfs_subr.c:2269
- sys/kern/vfs_subr.c:2552
- sys/kern/vfs_lock.c:703
- sys/vfs/ufs/ufs_vfsops.c:58
- sys/vfs/ufs/ffs_vfsops.c:1228
- sys/kern/vfs_default.c:1562
- sys/vfs/ufs/ffs_vfsops.c:270
- sys/kern/vfs_subr.c:2201
- sys/kern/vfs_syscalls.c:164
Detail
Exploit chain
none. No corruption primitive obtained on this non-DEBUG kernel: the UAF-free never occurs on the UFS mount-root vnode (mount holds a ref), and there are no INVARIANTS to convert the lock-protocol violation into a fault. The bug is gated behind mount/export privilege (sys_mount -> caps_priv_check_td; vfs_syscalls.c:164-168 requires privilege for MNT_EXPORTED), so it is not an unprivileged escalation. A root attacker on a DEBUG kernel (or with a vptofh-failing export fs) could in principle get a vnode UAF groomable into corruption, but that configuration is not present on this guest.
Evidence (decisive lines)
[*] target=/boot loops=16
[*] sizeof(ufs_args)=448 export_args=384
[+] done: vfs_setpublicfs UAF path reached ok=16 fail=0 (of 16)
[+] vfs_setpublicfs() ran the buggy :2261-vput -> :2269-VOP_PATHCONF path 16 time(s); on a non-DEBUG
kernel the unlocked-VOP races silently -- check serial console for a vnlru-reclaim UAF panic.
RUN_EXIT=0
--- guest alive ---
1:06AM up 25 mins, 0 users, load averages: 0.00, 0.00, 0.00
(stress run /boot 512 with vnode churn: ok=512/512, STRESS_EXIT=0, guest up; dmesg post-trigger: no vnode/lock/witness/panic warnings)
PoC changes
Rewrote expub.c substantially. The original passed a bare struct export_args (384 B) as the mount(2) data, but ffs_mount does copyin(data,&args,sizeof(struct ufs_args)) with struct ufs_args=448 B ({char *fspec; struct export_args export;}), so the original fed misaligned garbage and never reached the export path. The rewrite: (1) uses correct struct ufs_args (#include
Verified recommended fix
Keep rvp referenced+locked across vn_get_namelen() and vput exactly once on every path (success and each error). Concretely in vfs_setpublicfs (vfs_subr.c:2255-2295): (a) on the VFS_VPTOFH error at :2259 add vput(rvp) before return (closes the refcount leak); (b) remove the unconditional vput(rvp) at :2261 and instead vput once on the success path after the indexfile block, and convert the two error returns inside the indexfile block (:2270, :2288) to goto out; add an out: label that does vput(rvp); return error (closes the UAF/unlocked-VOP). fix.diff implements exactly this; git apply --check passes. Matches the finding proposal's intent (the proposal sketched the same goto-out shape); the verified diff makes the hunk line-numbers exact against master and adds the missing vput on the VPTOFH-error path explicitly.
Verdict
NOT REPRODUCED as an observable fault on this kernel, but the defect is REAL, REACHABLE, and the vulnerable code path is CONFIRMED to execute on the audited master DEV kernel. This is NOT a false positive and NOT already-fixed: master DEV (committed 2026-06-22, blame 6cc80ee9) contains the exact buggy lines unchanged. The vulnerable sequence VFS_ROOT(rvp ref'd+locked, vfs_subr.c:2255 via ufs_vfsops.c:58->vget) -> VFS_VPTOFH -> vput(rvp) (vfs_subr.c:2261; vput=vn_unlock+vrele, vfs_lock.c:703, drops BOTH lock and ref) -> vn_get_namelen(rvp)->VOP_PATHCONF(rvp) (vfs_subr.c:2269/2552) on the now-unlocked/unref'd rvp ran end-to-end 520/520 times across my runs with NO panic. No panic because: (1) the X86_64_GENERIC kernel has NO INVARIANTS/WITNESS (sysctl kern.conftxt confirms) to catch the unlocked-VOP_PATHCONF lock-protocol violation; (2) the UFS mount-root vnode retains references through the race window so vnlru never reclaims it (the actual UAF-free never occurs on this vnode). The deterministic refcount-leak variant (:2259, VFS_VPTOFH error return without vput) needs a filesystem whose vptofh fails, but UFS ffs_vptofh (ffs_vfsops.c:1228) always returns 0, and none of the export-capable fs types on this guest (hammer2/devfs/ufs/null/tmpfs) have a failing vptofh; procfs lacks vptofh (vfs_stdvptofh->EOPNOTSUPP, vfs_default.c:1562) but does not process export args, so the leak variant is unreachable here. On a DEBUG/INVARIANTS kernel this panics in VOP_PATHCONF on the unlocked vnode; on a default GENERIC kernel it races silently. The fix.diff is warranted.