# DF-3022 — devfs lock-order inversion: nresolve-under-ncp vs unlinkp-under-devfs_lock → unprivileged kernel deadlock (DoS)

## What

`cache_resolve()` holds the parent namecache lock (`_cache_get(par)`,
sys/kern/vfs_cache.c:4404) **across** `VOP_NRESOLVE` (vfs_cache.c:4418).
`devfs_vop_nresolve()` then acquires `devfs_lock` EXCLUSIVE while that ncp
is locked (sys/vfs/devfs/devfs_vnops.c:474).

Concurrently, device teardown on the devfs core thread runs under
`devfs_lock` EXCLUSIVE: `devfs_unlinkp()` calls
`cache_inval_vp(vp, CINV_DESTROY)` (sys/vfs/devfs/devfs_core.c:626-627),
which zaps the very ncp's associated with the dying device node
(`cache_zap`, vfs_cache.c:2884) — spinning to acquire them.

AB-BA:

```
lookup thread : ncp(EX)  ───────────► devfs_lock(EX)     [vnops.c:474]
core thread   : devfs_lock(EX) ─────► ncp (cache_zap)    [core.c:627]
```

When both pick the same /dev/pts/N entry, the core thread spins forever
inside `cache_zap` ("indefinite wait"), every process resolving any /dev
path piles up behind the jammed ncp, and the system wedges (sshd, shells,
everything that touches /dev — e.g. `/dev/urandom` — hangs). Guest becomes
unreachable: hard DoS.

## Trigger (unprivileged)

Same racer as DF-3017: `open`/`close` of `/dev/ptmx` (pty clone churn →
destroy_dev → cache_inval under devfs_lock) racing `lstat`/`access` of
`/dev/pts/N` (cache_resolve holds ncp across VOP_NRESOLVE → devfs_lock).

On the **stock** kernel this interleaving usually manifests as DF-3017's
sysref panic instead (the node is freed while the lookup is inside the
allocv window). With DF-3017 fixed (patched kernel #1, Sep 5 2026
10:33:01), the racer survived ~6 minutes and then deadlocked the kernel
with exactly this signature (2nd bug no longer masked by the 1st):

```
devfs: race avoided node '(null)' (0xfffff8011759eb10)
devfs: race avoided node '1' (0xfffff801184cf2a0)
spin_lock_ex: cache_zap, indefinite wait (1 secs)!
[diagnostic] cache_lock_shared: df3017 blocked on 0xfffff8011867e900 "0"
[diagnostic] cache_lock_shared: df3017 blocked on 0xfffff8011867e900 "0"
[diagnostic] cache_lock_shared: df3017 blocked on 0xfffff8011867e900 "0"
[diagnostic] cache_lock_shared: sshd-session blocked on 0xfffff8011938cf00 "urandom"
```

…then the guest stopped responding entirely (wedged, not panicked).

## Evidence

* `wedge-console.txt` — full serial-console transcript of the wedged run
  (patched-for-DF-3017 kernel, same unprivileged racer, ~6 min in).

## Impact

Unprivileged local user → whole-system deadlock (all /dev access hangs,
sshd dies). No memory corruption — a pure livelock/deadlock DoS, but
trivially triggerable and 100% fatal.

## Recommended fix (upstream discussion needed — protocol-level)

Break the cycle by not acquiring ncp locks while holding devfs_lock: e.g.
have `devfs_unlinkp()` perform the `cache_inval_vp()` *after* releasing
`devfs_lock` (the vp is referenced by node->v_node across the window), or
make `devfs_vop_nresolve()` drop-and-retry when the destroy side is
active. This needs careful analysis of devfs_core's serialization
assumptions (overlap with the fresh devfs_core pass-2 findings
DF-3005..3008) — flagged for upstream rather than patched here.

## Reproduce

See findings/poc/DF-3017/df3017.c (identical racer); run
`./df3017 480` as any user on a kernel where DF-3017 is fixed, or long
enough on stock to miss the panic window.
