# DF-5832 VERDICT — NOT REPRODUCED on this guest (race-dependent); root cause code-proven

## Bottom line

The race did **not** land within ~**80,000 rounds** across four rig designs
on the 6-vCPU guest, so I am honestly classifying this as
`not_reproduced` (impact as observed: none). The root cause, however, is
**structurally certain from the code** and I recommend fixing it on the
strength of the inspection proof — a permission-revocation mechanism that
can silently fail to take effect is not something to leave in a kernel
path every stat/open/exec traverses.

## What is proven (by code + controls)

1. **The fast path trusts the flag with no revalidation**
   (vfs_nlookup.c:1616-1621): `nflags == NLC_EXEC && nflagsp == NULL &&
   (ncp->nc_flag & NCF_WXOK)` → `return 0` — the VEXEC directory-search
   check is skipped entirely.

2. **The setter is unsynchronized with the clearer.** The setter
   (naccess, vfs_nlookup.c:1720-1762) computes `cflags` from a lockless
   `VOP_GETATTR_LITE` snapshot and writes `ncp->nc_flag` while holding
   the ncp only **shared** (locked at :1630, held through the write to
   :1771). The clearer (`cache_inval_wxok`, vfs_cache.c:2054-2070,
   called from `setfmode`, vfs_syscalls.c:3422, immediately after
   `VOP_SETATTR`) takes **no ncp lock at all** — only `vp->v_spin`.
   There is therefore no ordering between a concurrent clear and set:
   `clear → stale-set` is an allowed interleaving, and the stale set
   wins because nothing rechecks the mode while the flag stands.

3. **The observable halves of the flag lifecycle work** (controls, see
   README): an unprivileged `stat` of a file inside a `0700 root:wheel`
   formerly-0777 directory correctly returns `EACCES` after a quiet
   chmod, and succeeds while the directory is 0777.

4. **Why the straddle is hard to hit.** For the resurrection the setter
   must be stalled (interrupt/preemption) inside its ~150ns
   `GETATTR → atomic_set` span for longer than the chmod's
   `mode-commit → cache_inval_wxok` gap (~0.3–2 µs), with the mode
   commit landing inside that span. My rigs produced straddle
   candidates (burst-timed one-shot workers with the commit timed into
   the in-flight window) and amplified in-span preemption (cross-CPU
   IPI storm via mmap/munmap churn + ping flood), but the joint
   probability evidently remained below what ~80k rounds sampled on a
   virtualized guest.

## Rig summary (what was tried)

| Run | Design | Rounds | Result |
|-----|--------|--------|--------|
| run.3.log | per-round fork burst, 100 workers, late chmod | ~40,000 | no win |
| run.2.log | persistent pool + pipe burst, 14 workers, chmod at W+4µs (commit landed *after* the burst — design flaw found and fixed) | ~8,000 | no win |
| run.log | pool + pipe burst, 24 workers, chmod timed mid-window (commit inside the in-flight span window), **plus IPI storm + ping flood** | ~68,000 | no win |

Worker op: `unlink("D/nope")` (nonexistent) — a modifying lookup
(`NLC_DELETE` ⇒ `naccess(D, NLC_EXEC, &dflags)` full path ⇒ GETATTR +
flag write), and `DELETE`-on-nonexistent skips the parent-write
recursion (vfs_nlookup.c:1571-1575 requires `nc_vp != NULL` for
NLC_DELETE), so a straddling worker's write is the last one in its own
syscall. Probe: `stat("D/probe")` as nobody — succeeds only via the
resurrected flag (shortcut), `EACCES` otherwise.

## Impact if manifested (theoretical, per the root cause)

Directory search-permission revocation does not take effect: an
unprivileged user retains traversal/list access to a directory after
`chmod 0700`, indefinitely (until the next chmod of that directory),
reaching files inside it whose own modes permit access. Only the
directory VEXEC check is bypassed (create/delete still check the real
directory write permission).

## Fix (proposed; not build-validated since the race did not reproduce)

`fix.diff`: serialize the clear with the setter — `cache_inval_wxok()`
locks each ncp exclusively before clearing (restart-safe scan holding
only an ncp reference across the lock), and `setfmode()` drops the
vnode lock (keeping a reference) before calling it, because taking the
ncp exclusive lock under a held vnode lock would invert the ncp-first
lock order against `naccess()`'s shared-ncp → `vget`(shared-vp)
sequence. With this ordering, any setter whose GETATTR predates the
mode change has its write ordered before the clear (it holds the ncp
shared across GETATTR→atomic_set), so the clear is always the last
write.

## Kernel references

* sys/kern/vfs_nlookup.c:1616-1621 — fast path trusts NCF_WXOK unchecked
* sys/kern/vfs_nlookup.c:1720-1723 — NCF_WXOK computed from GETATTR_LITE snapshot
* sys/kern/vfs_nlookup.c:1758-1762 — flag write under shared ncp lock
* sys/kern/vfs_nlookup.c:1630-1658 — shared lock spans GETATTR→write
* sys/kern/vfs_cache.c:2054-2070 — cache_inval_wxok takes no ncp lock
* sys/kern/vfs_syscalls.c:3411-3424 — setfmode: SETATTR then inval
* sys/vfs/tmpfs/tmpfs_vnops.c:404-431 — lockless GETATTR_LITE (tmpfs)
* sys/vfs/hammer2/hammer2_vnops.c:370-424 — seqlocked GETATTR_LITE (hammer2)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
