# DF-3158 — deget() publishes the denode in the hash before taking its reference (duplicate-denode TOCTOU)

`deget()` (sys/vfs/msdosfs/msdosfs_denode.c:308) inserts the new denode
into the global hash — releasing `dehash_token` inside `msdosfs_hashins` —
and only sets `ldep->de_refcnt = 1` at :323, outside the token. Both
`msdosfs_hashget()` (:131) and `msdosfs_hashins()` (:178) treat
`de_refcnt <= 0` entries as absent. A concurrent `deget()` for the same
`(dev, dirclust, diroffset)` on another CPU that scans the bucket inside
that window sees `de_refcnt == 0`, concludes no denode exists, and appends
its own — two vnodes/denodes for one directory entry. Consequences if
won: divergent `de_FileSize`/`de_StartCluster` caches, two `deupdat`
writers for one dirent slot (stale-denode clobber), writes through a stale
denode into freed/reallocated clusters (cross-linked files on multi-user
volumes), INVARIANTS `usemap_alloc` KASSERT on later allocation.

The hammer races parallel `open()`s of one file and detects a duplicate
pair by persistent size divergence: thread 0 ftruncates (publishing the
round only on success), checkers stat their own fd and confirm any
divergence by truncating via a fresh fd and re-reading — a true duplicate
never converges, a transient does.

## Outcome

NOT REPRODUCED in bounded runs: 2 x ~2-4M synchronized deget pairs on
6 vCPUs, 0 confirmed persistent duplicates on both the stock and the
patched kernel. An earlier detector version reported thousands of "hits";
hardening the detector (publish-only-on-success + convergence
confirmation) showed every divergence was transient and self-correcting —
the initial "reproduction" was a measurement artifact and is documented
here as a lesson. The code-level window analysis stands (the refcnt store
is genuinely outside the token-protected insertion); the window appears
too narrow to win even under saturated parallel lookup load. Reported as
speculative; the one-line ordering fix is included in DF-3157's fix.diff
and is pure hardening.
