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

Racing file creates against cross-parent msdosfs directory renames leaks mount/vnode references: umount(2) hangs forever and the system wedges (unprivileged local DoS)

Summary

Two unprivileged processes racing on any writable msdosfs directory - one performing cross-parent DIRECTORY renames (each leaves the target directory unlocked for the whole doscheckpath() ancestor walk, :822-863, incl. per-hop deget/vput at :857-861), the other performing ordinary create/unlink churn - reproducibly (2/2, once attribution-clean on a fresh reset guest) leave the mount with leaked references (kernel diagnostic: 'unmount(/fatrace): ... 2 mount refs still present'). After all racers exit (verified zero leftover processes), umount hangs forever, and the guest goes fully catatonic: sshd stops accepting connections, serial console frozen, no panic and no DDB - hard system wedge requiring power-off. Control run with identical churn against same-directory FILE renames (no doscheckpath) unmounts instantly and fscks clean, isolating the trigger to the directory-rename path. The kernel's own race detector fired during the churn ('kern_rename: retry due to race', vfs_syscalls.c:4323), proving the interleaving is live. Any local user with write access to a single directory on any mounted msdosfs filesystem (removable FAT media, DOS partition, usermount image) can wedge the entire machine in ~30s using only mkdir/rename/open/unlink. Sequentially every hand-traced path is balanced (incl. deget clearing *depp on error); the leak manifests only under interleaving - exact leaking statement needs a refcount-instrumented kernel build (prime suspects: the vref(tdvp)+doscheckpath-vput pairing under kern_rename EAGAIN retry; relookup-found-tvp combinations; deget error path leaving a hash-resident denode). Availability-only, system-wide, non-recoverable without power cycle.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3117 Β· 13 files
FileTypeDescriptionSize
trigger.c β€” 7.4 KB view raw
build.sh β€” 66 B view raw
run.sh β€” 130 B view raw
wedge.sh β€” 1.7 KB view raw
build.log β€” 304 B view raw
run.3.log β€” 1.7 KB view raw
run.5.log β€” 1.8 KB view raw
run.9.log β€” 1.9 KB view raw
run.10.wedge.log β€” 850 B view raw
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
verdict.json β€” 4.0 KB view raw
manifest.json β€” 985 B view raw

DF-3117 β€” racing create vs cross-parent msdosfs directory rename wedges the system

What

Two unprivileged processes on any writable msdosfs directory:

  • R: cross-parent directory renames (bouncer: mkdir D in p1, rename into a deep parent p2 β€” every rename runs doscheckpath() with the target directory unlocked), and
  • C: a create/stat/unlink churn loop (creator)

race for 30–60 s. All racer processes exit cleanly, but afterwards:

  1. umount of the filesystem hangs forever (leaked mount/vnode references β€” the earlier console diagnostic printed unmount(/fatrace): ... 2 mount refs still present), and
  2. the guest then goes catatonic: sshd stops accepting connections ("Connection timed out during banner exchange"), the serial console freezes, and vm.sh status reports down while the QEMU process is still alive. No panic, no DDB β€” a hard system wedge.

Reproduced twice (run.9 and the attribution-clean run.10), on a fresh vm.sh reset with-src guest with a fresh image and verified-zero leftover processes before the umount.

Control: the Window-B configuration (file renames only, no doscheckpath) unmounts instantly after identical churn (run.3.log / run.5.log stage [6]) β€” isolating the trigger to the directory-rename/doscheckpath path.

Where (kernel_refs)

The leak site is not line-pinned (needs VFS refcount instrumentation); the racing surface spans:

PoC

Same harness as DF-3116 (trigger.c, wedge.sh). The decisive run is run.10.wedge.log: racers done + "leftover procs: none" + umount watchdog never completing + ssh channel death.

No fix.diff: a correct fix requires pinpointing the leaking statement (upstream should reproduce under a VFS_REFCOUNT/debug-refcount kernel build). Recommended containment while unfixed: none realistic; severity is availability of the whole system, triggered by any local user with write access to one msdosfs directory.

VERDICT.md
↓ download raw

DF-3117 VERDICT β€” racing create vs cross-parent dir rename: mount-ref leak β†’ umount hang β†’ system wedge

Status: REPRODUCED (impact=dos). Twice; the decisive run is attribution-clean.

How it reproduces

  1. Fresh vm.sh reset with-src guest (stock INVARIANTS kernel #0, 6 vCPU), fresh 512 MB FAT32 image on /dev/vn0, mounted -u 65534 -g 65534 -m 0777 at /fatrace.
  2. p1 = /fatrace/p1 (shallow); p2 = /fatrace/deep/l1/../l100 (a 100-deep chain, so every doscheckpath() walk is ~100 hops).
  3. Three unprivileged (nobody) racers for 30 s: - bouncer: 38,661 cross-parent directory renames (mkdir D in p1, rename into p2) β€” all succeeded (0 errors), - creator in p2: 58,608 create+verify+rotate cycles β€” 0 vanished, - creator in p1: 41,422 cycles β€” 0 vanished.
  4. All racers exit; ps confirms zero leftover processes ("leftover procs: none").
  5. umount /fatrace under a 20 s watchdog never completes; the ssh channel then dies ("Connection timed out during banner exchange"), the serial console freezes (no further output, no panic, no DDB), and vm.sh status reports down while the QEMU process stays alive. See run.10.wedge.log (cut off mid-umount by the wedge itself) and run.9.log for the first occurrence.

First occurrence (run.9, same race shape, 60 s): identical β€” umount stage hung silently, guest later went unresponsive; the console from that session additionally showed the kernel diagnostic unmount(/fatrace): ... 2 mount refs still present (from a subsequent forced umount attempt), i.e. two leaked mount references, and kern_rename: retry due to race on: "D68634" -> "D68634" (12,0) (sys/kern/vfs_syscalls.c:4323), proving the rename race machinery was live during the churn.

Control (isolates the trigger)

The Window-B configuration β€” identical creator churn against same-dir file renames (no directory renames, hence no doscheckpath) β€” unmounts instantly and fsck's clean (run.3.log / run.5.log stage [6]). The wedge therefore requires the cross-parent directory rename path (msdosfs_rename's doscheckpath/relookup unlock windows + deget/vput ancestor walk), racing ordinary creates.

Root cause status

The leaking statement is not line-pinned. Sequentially, the ref protocol in msdosfs_rename/doscheckpath/vop_compat_nrename is balanced on every path I hand-traced (including doscheckpath's brelse/deget error contract β€” deget clears *depp on all error returns, msdosfs_denode.c:291,318,374). The leak therefore manifests only under a specific interleaving (candidates: the vref(tdvp)+doscheckpath vput pairing under kern_rename EAGAIN retry, the relookup-found-tvp combinations at vnops:1056-1102, or deget's error-path denode left hash-resident at msdosfs_denode.c:363-376). Pinning it requires a VFS refcount-debug kernel build; recommended first step upstream.

Impact

Any local user with write access to a single directory on any mounted msdosfs filesystem (USB stick, FAT partition, user-mounted image with vfs.usermount=1) can, in ~30 s of unprivileged racing, leak mount references such that the filesystem can never be unmounted and the system subsequently wedges entirely (no panic, no dump β€” hard catatonia). Availability-only; confidentiality/integrity untouched by this aspect (the integrity consequence of the same racing surface is DF-3116).

Fix

No fix.diff β€” no line-pinned root cause. Upstream should reproduce under refcount instrumentation (e.g. DEBUG_VFS_LOCKS / VFS_REFCOUNT equivalents) on the bouncer+creator workload in wedge.sh; prime suspects listed above. fix_status=not_testable for the same reason.

Guest state

The wedge dirtied the guest twice; vm.sh reset with-src was run afterward and the guest is up and clean (source tree verified restored, no leftover artifacts beyond /tmp copies removed by the reset).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

No fix authored: the leaking statement is not line-pinned (sequentially-balanced ref protocol on every hand-traced path; leak manifests only under a specific interleaving). Requires refcount-instrumented kernel build upstream.

per-fix-DF-3117

Confirmed kernel references

Detail

Exploit chain

unprivileged user with write access to one directory on any mounted msdosfs -> run bouncer (cross-parent dir renames into a deep parent) + creator (create churn) for ~30s -> mount/vnode references leak -> umount(2) of the filesystem hangs forever -> system-wide wedge (all userland eventually starves; hard poweroff required).

Evidence (decisive lines)

["findings/poc/DF-3117/run.10.wedge.log β€” decisive attribution-clean run: racers exit, 'leftover procs: none', umount watchdog never completes, ssh channel dies mid-probe", 'findings/poc/DF-3117/run.9.log β€” first occurrence: same shape, umount stage hang', 'findings/poc/DF-3117/VERDICT.md β€” full narrative incl. console diagnostics (unmount ... 2 mount refs still present; kern_rename: retry due to race) and the control run', 'findings/poc/DF-3117/wedge.sh β€” exact reproducible script']

PoC changes

Same harness as DF-3116 (trigger.c authored from scratch; see DF-3116 poc_changes). wedge.sh added for attribution-clean single-shot reproduction: fresh guest, fresh image, racers verified dead, umount under 20s watchdog.

Verified recommended fix

Reproduce under VFS refcount instrumentation on the wedge.sh workload and fix the leaking statement in the msdosfs rename/doscheckpath/compat chain; until then this is an unprivileged full-system availability bug.

Verdict

Reproduced twice (once attribution-clean on a fresh vm.sh reset with-src guest): 30s of unprivileged racing β€” 38,661 cross-parent DIRECTORY renames (each running doscheckpath with tdvp unlocked) against ~100k file creates in the target parents, all on a stock FAT32 image β€” leaves the msdosfs mount with leaked references (kernel diagnostic showed '2 mount refs still present' in the first occurrence). After all racers exit (verified zero leftover processes), umount hangs forever and the guest subsequently goes fully catatonic: sshd stops accepting (banner timeout), serial console freezes, no panic, no DDB; vm.sh reports down while QEMU stays alive. Control run with identical creator churn against same-dir FILE renames (no doscheckpath) unmounts instantly and fscks clean, isolating the trigger to the directory-rename/doscheckpath/deget path. The exact leaking statement is not line-pinned (sequentially-balanced ref protocol on all hand-traced paths, incl. deget's clears-*depp-on-error contract); needs refcount-instrumented kernel build upstream.