devfs lock-order inversion: VOP_NRESOLVE holds ncp across devfs_lock while destroy path holds devfs_lock across cache_inval_vp/cache_zap of the same ncp β unprivileged whole-system deadlock
| Field | Value |
|---|---|
| ID | DF-3022 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:N/I:N/A:H |
| CWE | CWE-667 (lock-order inversion β deadlock) |
| File | sys/vfs/devfs/devfs_vnops.c |
| Lines | 474 (destroy side: devfs_core.c:626-627) |
| Area | vfs/devfs |
| Confidence | likely |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:vfs |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
cache_resolve holds the parent namecache lock across VOP_NRESOLVE; devfs_vop_nresolve then takes devfs_lock EX under that ncp. The devfs core destroy path holds devfs_lock EX across devfs_unlinkp's cache_inval_vp(vp, CINV_DESTROY), whose cache_zap spins to acquire the same /dev/pts/N ncp. Unpriv ptmx-churn + /dev/pts stat races produce a circular wait: cache_zap spins indefinitely, every /dev path resolution system-wide blocks (sshd included β observed blocked on /dev/urandom), guest dies. Any local user; reproduced as a full kernel wedge ~6 min into the DF-3017 racer on the DF-3017-patched kernel (where the sysref panic no longer masks it): console 'spin_lock_ex: cache_zap, indefinite wait' + cascading cache_lock_shared blocks ending with sshd-session stuck on /dev/urandom; guest unreachable. On stock kernels the same interleaving usually manifests as DF-3017's panic first. Pure DoS, no corruption. Fix: do not acquire ncp locks while holding devfs_lock in the destroy path (move cache_inval_vp outside devfs_lock β node->v_node keeps the vp referenced across the window), or drop-and-retry in nresolve. Protocol-level change overlapping the devfs_core pass-2 findings β flagged for upstream analysis rather than patched in this run.
Timeline
- 2026-09-02 Discovered during pass-2 audit of devfs_vnops.c (GLM 5.3); system-wide wedge reproduced on the DF-3017-patched kernel.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3022 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| wedge-console.txt | β | 2.4 KB | view raw | |
| verdict.json | β | 3.0 KB | view raw |
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.
Fix verification
not_testableConfirmed kernel references
Detail
Exploit chain
open/close /dev/ptmx churn (destroy thread: devfs_lock EX -> cache_inval_vp -> cache_zap spins on ncp of /dev/pts/N) || concurrent lstat(/dev/pts/N) (lookup thread: ncp EX held by cache_resolve -> VOP_NRESOLVE -> devfs_lock EX blocked) -> circular wait -> every /dev path resolution system-wide blocks -> sshd/shells hang -> guest dead
Evidence (decisive lines)
['findings/poc/DF-3022/wedge-console.txt - spin_lock_ex cache_zap indefinite wait + cache_lock_shared cascade + sshd-session blocked on /dev/urandom, guest wedged', 'sys/kern/vfs_cache.c:4404-4418 - ncp held locked across VOP_NRESOLVE', 'sys/vfs/devfs/devfs_core.c:626-627 - cache_inval_vp under devfs_lock']
PoC changes
No separate PoC; the DF-3017 racer (findings/poc/DF-3017/df3017.c) triggers this once DF-3017's panic is out of the way.
Verified recommended fix
Perform cache_inval_vp() outside devfs_lock in devfs_unlinkp() (node->v_node keeps the vp referenced across the window), or make devfs_vop_nresolve() release/retry when the destroy side holds devfs_lock; requires upstream analysis vs devfs_core serialization.
Verdict
Unprivileged pty-churn + /dev/pts stat race deadlocks the kernel: cache_resolve holds the parent ncp lock across VOP_NRESOLVE while devfs_vop_nresolve takes devfs_lock (vnops.c:474), and the devfs core destroy path holds devfs_lock across devfs_unlinkp's cache_inval_vp -> cache_zap on the same ncp (devfs_core.c:626-627). Observed as a full system wedge (~6 min into the racer on the DF-3017-patched kernel #1, where the competing sysref panic no longer masks it): spin_lock_ex 'cache_zap, indefinite wait', then [diagnostic] cache_lock_shared blocks for df3017 and finally sshd-session on /dev/urandom, guest unreachable. On the stock kernel the same interleaving overwhelmingly manifests as DF-3017's panic instead, which is why this second bug was invisible before. Pure deadlock DoS, no corruption.
No comments yet.