β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3022

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3022 Β· 3 files
FileTypeDescriptionSize
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.

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_testable
per-fix-DF-3022

Confirmed 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.