# DF-3134 — dirfs_node_setpassive() check-then-close of dn_fd races
# dirfs_findfd()/consumer use: closed (and host-recycled) descriptor used
# against the WRONG host file

## What

Nothing in dirfs synchronizes `dn_fd` lifetime. `dirfs_node_setpassive(dmp,
dnp, 0)` (dirfs_subr.c:848-857) checks `dirfs_node_refcnt(dnp) == 2 && …`
and then `close(dnp->dn_fd); dnp->dn_fd = DIRFS_NOFD;` — while another CPU
can be inside `dirfs_findfd()` (walk reads `dnp1->dn_fd` at :470, the node
ref happens only afterwards at :486) or inside a consumer that uses
`pathnp->dn_fd` after findfd returned (dirfs_alloc_file :194/:202, getattr
vnops:391/:393, readlink vnops:1329, nremove's unlinkat vnops:909, nmkdir's
mkdirat vnops:1051-1058, open_helper :602). If the close wins the race:

* without fd recycling: the consumer's openat/unlinkat/fstatat hits a closed
  descriptor → EBADF (and in dirfs_alloc_file the `openat()==-1` path
  re-enters the DF-0856 node-leak error path);
* with fd recycling (the vkernel opens host files constantly): the consumer
  operates on **whatever host file now owns that fd number** — metadata and
  create/unlink operations land in the WRONG directory/file, bounded only by
  the vkernel process's host credentials.

Sibling of DF-3060 (which owns the unlocked dm_fdlist *walk* UAF in
nresolve); this finding is the *fd lifetime* race with a different, worse
failure mode (wrong-file operations via a recycled descriptor).

## Reproduce

dirfs is vkernel-only → deterministic orchestrated transcription (DF-3054
precedent): verbatim findfd walk condition and setpassive close branch, real
descriptors, real openat/fstatat/O_CREAT, three threads synchronized by step
barriers reproducing the exact unsynchronized interleaving:

```
ssh dfbsd-maxx        # unprivileged (uid 1001)
cd poc/DF-3134
./build.sh && ./run.sh   # expect rc=2
```

Expected (run.log):
* variant 1 — consumer used fd=4; setpassive closed it; recycler got fd=4;
  consumer's `openat(stale_fd, "sentinel")` **read the DECOY directory's
  file**; its O_CREAT **created a file in the DECOY directory**; the
  intended directory untouched;
* variant 2 — no recycler: openat on the closed fd → EBADF
  (use-after-close);
* variant 3 — FIXED (dm_token held across findfd..dropfd and across
  setpassive's check+close): consumer reads the CORRECT sentinel, creation
  lands in the intended directory, no EBADF.

3/3 runs byte-identical (run.log, run.2.log, run.3.log — same MD5).

## Fix

`fix.diff`: `lwkt_gettoken(&dmp->dm_token)` in dirfs_findfd (released by
dirfs_dropfd for non-NULL node arguments, i.e. exactly the findfd callers)
and across setpassive's check+close section. LWKT tokens are reentrant per
thread, so the nested acquisitions (nremove holds findfd's token when it
calls setpassive; node_free→setpassive under dropfd) are safe. Applies
cleanly (git apply --check RC=0 local + guest /usr/src), compile-neutral
(identical first error, pre-existing errno.h env breakage). A full
passive-cache lockdown belongs with DF-3060.
