umount -f force-close never passes FREAD: fifo fifoinfo + connected socket pair leak permanently
Summary
vclean_vxlocked()'s force-close loop (vfs_subr.c:1341-1352) only ever passes FWRITE|FNONBLOCK or bare FNONBLOCK to VOP_CLOSE - never FREAD - so fifo_close() cannot decrement fi_readers, the counters-both-zero destroy condition never fires, and the fifoinfo plus its connected AF_LOCAL socket pair (socreate x2 + unp_connect2, :188-222) leak permanently whenever a fifo is still open when a mount is force-unmounted (or forced shutdown tears down mounts). The later userspace close() routes to dead_close() which never reaches the fifo counters. Root-triggered permanent kernel resource leak, unbounded with repetition (2 sockets + ~1KiB per orphaned fifo); pre-condition reachable without privilege via a fifo fd pinned by an in-flight SCM_RIGHTS message no process owns (unmount kill protocol cannot close it). VERIFIED stock: netstat -f unix stays +2 permanently; control returns to baseline. Fix validated (VFIFO-aware force-close flags + counter clamps): returns to baseline, normal fifo I/O unaffected.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3161 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| umount_fifoleak_scmrights.c | β | 3.6 KB | view raw | |
| fifo_hold.c | β | 834 B | view raw | |
| umount_fifoleak.sh | β | 2.3 KB | view raw | |
| run.log | β | 1.6 KB | view raw | |
| fix_run.log | β | 1.0 KB | view raw | |
| env.txt | β | 264 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| README.md | β | 2.4 KB | β raw |
DF-3161 β umount -f force-close never balances fifo reader/writer counts (fip + socket pair leaked)
Root cause
vclean_vxlocked() (sys/kern/vfs_subr.c:1341-1352) force-closes a vnode
with open descriptors by looping VOP_CLOSE() until v_opencount == 0,
but it only ever passes FWRITE|FNONBLOCK (when v_writecount) or bare
FNONBLOCK β never FREAD. fifo_close()
(sys/vfs/fifofs/fifo_vnops.c:610-614) decrements fi_readers only when
FREAD is passed, so after a forced unmount the reader count can never
reach 0, the fi_readers == 0 && fi_writers == 0 destroy condition
(fifo_vnops.c:620-622) never fires, and the fifoinfo plus its connected
AF_LOCAL socket pair (socreate x2 + unp_connect2, fifo_vnops.c:188-222)
leak permanently. The later userspace close() goes to dead_close()
(sys/vfs/deadfs/dead_vnops.c:118-133), which never touches the fifo
counters.
Reaching the force-close path
dounmount(MNT_FORCE) first tries to make holders exit: it SIGINTs
(retry 3) then SIGKILLs (retry 7) every process using the mount
(sys/kern/vfs_syscalls.c:929-946) and waits up to 50 retries x 0.25 s.
An open fifo that no process owns β e.g. an fd passed over a
unix-domain socket (SCM_RIGHTS) and never received β is pinned by the
socket buffer, invisible to the kill protocol, so the retries exhaust and
the mount is ripped with the fifo still open (console warns
forced umount of ... - N namecache refs), running the force-close loop.
PoC
umount_fifoleak_scmrights.c (deterministic; the one that reproduces):
open a fifo O_RDWR on a tmpfs, hide the fd inside a socketpair via
SCM_RIGHTS, umount -f, drop the socket, count connected unix-domain
stream sockets with netstat -f unix.
umount_fifoleak.sh + fifo_hold.c: first attempt (process-held fd) β
does NOT leak because dounmount's SIGINT kills the holder cleanly;
kept as the control/characterization run.
Build / run (as root on the guest)
cc -O -Wall -o leak_scm umount_fifoleak_scmrights.c ./leak_scm
Expected output (bug present)
baseline: 4 stream sockets fifo-open (+2 expected): 6 stream sockets ... umount rc=0 (after ~13 s of exhausted retries; console warns) after umount -f + close (leak: stays +2): 6 stream sockets <-- LEAK
Expected output (fixed kernel)
after umount -f + close ...: 4 stream sockets (back to baseline)
DF-3161 VERDICT β reproduced (permanent kernel resource leak on umount -f)
Reproduced? Yes.
Deterministic PoC (umount_fifoleak_scmrights.c, run as root on the stock
INVARIANTS guest kernel #0, 2026-09-07):
- baseline connected unix-domain stream sockets: 4
- fifo open on a fresh tmpfs: 6 (+2 = the internal socket pair)
- fifo fd hidden in a unix-socket receive buffer (SCM_RIGHTS, never received) so no process owns it
umount -f: dounmount's SIGINT/SIGKILL protocol (sys/kern/vfs_syscalls.c:929-946) cannot find a holder; 50 retries x 0.25 s exhaust; console printsforced umount of "(null)" - 1 namecache refs, 1 mount refs; the mount is ripped.- close the holding socketpair, wait 2 s: still 6 β the socket pair is never freed.
Control: the same fifo opened/closed normally (and even the process-held umount -f variant, where SIGINT kills the holder first) returns to the baseline count of 4. The leak is specifically the force-close path.
Why (line-level)
vgone_vxlocked->vclean_vxlocked(vp, DOCLOSE)(sys/kern/vfs_subr.c:1285, 1567).- Force-close loop
sys/kern/vfs_subr.c:1341-1352: passes onlyFWRITE|FNONBLOCK(ifvp->v_writecount) orFNONBLOCK. FREAD is never passed. fifo_close(sys/vfs/fifofs/fifo_vnops.c:610-614):fi_readers--happens only underap->a_fflag & FREAD-> stays 1.- Destroy condition
fi_readers == 0 && fi_writers == 0(fifo_vnops.c:620-622) never fires;v_fifoinfoand the connected socket pair (socreate x2 + unp_connect2, fifo_vnops.c:194-216) leak. - The later userspace close of the (now dead) vnode goes to
dead_close(sys/vfs/deadfs/dead_vnops.c:118-133), which only clamps v_opencount/v_writecount and never reaches the fifo layer.
For the O_RDWR fd in the PoC the first (and only) loop iteration passes
FWRITE|FNONBLOCK, decrementing only fi_writers; fi_readers remains
1 forever. Each repetition of the scenario leaks 2 sockets + 1
fifoinfo (~1 KiB) β unbounded with repeated mounts.
Impact
Permanent kernel memory/socket leak, root-triggered (umount -f,
shutdown of mounts with orphaned fifo descriptors β SCM_RIGHTS in
flight, descriptor held by a dying-but-unkillable process, etc.).
Not memory corruption: the leaked objects stay referenced and consistent.
Fix validation (see run.2.log / fix logs)
fix.diff (this directory) makes the force-close loop VFIFO-aware
(FREAD|FWRITE / FREAD flags) and clamps fifo_close's decrements.
Applied to a clean-source guest, kernel rebuilt with
make nativekernel, guest rebooted:
- baseline PoC behavior pre-patch (stock kernel #0): leak reproduced (6 sockets persisting; see run.log).
- post-patch:
after umount -f + close ...: 4 stream socketsβ back to baseline, leak gone; no panic; normal fifo open/close/read/write and the SIGINT-kill protocol unaffected (regression checks in fix_run.log).
Chain summary
unpriv user -> NOT applicable; requires root's umount -f (or shutdown) + a fifo fd that no process can be forced to close. Pure resource leak.
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.
Fix verification
fixedOn the rebuilt+rebooted kernel #1 with fix.diff applied, the identical PoC returns the socket count to baseline (4) - the leak is gone; normal blocking/O_RDWR fifo I/O and the umount kill protocol regression-checked clean.
fix_run.log (patched-kernel run + regression), kbuild BUILD-RC=0, kinstall INSTALL-RC=0
Confirmed kernel references
Detail
Exploit chain
root mounts tmpfs; unpriv-side primitive: any process that gets a fifo fd into a unix-socket buffer (SCM_RIGHTS, unreceived) on a mount that is later force-unmounted (root action or shutdown) leaks 2 sockets + 1 fifoinfo per fifo, permanently
Evidence (decisive lines)
['run.log: baseline 4 -> open 6 -> umount -f + close: 6 (leak) vs control 4', 'fix_run.log: same scenario on patched kernel #1 -> 4 (baseline restored)', 'VERDICT.md: full line-level chain vclean->fifo_close->destroy-condition']
PoC changes
First attempt (process-held fd, umount_fifoleak.sh) did not leak because dounmount SIGINTs holders cleanly; rewrote as umount_fifoleak_scmrights.c pinning the fd with an unreceived SCM_RIGHTS message so the force-close path actually runs - deterministic reproduction.
Verified recommended fix
vclean_vxlocked: pass FREAD|FWRITE (writecount>0) or FREAD for VFIFO force-closes; clamp fifo_close decrements so mixed opens stay balanced.
Verdict
Reproduced on the stock INVARIANTS guest kernel #0: a fifo whose descriptor is pinned by an in-flight SCM_RIGHTS message (owned by no process, so dounmount's SIGINT/SIGKILL protocol cannot reach it) survives umount -f; the exhausted-retry forced unmount runs vclean_vxlocked(DOCLOSE), whose close-flag selection (vfs_subr.c:1341-1352) never passes FREAD, so fifo_close cannot decrement fi_readers and the fifoinfo + its connected AF_LOCAL socket pair leak permanently (netstat -f unix stays +2 above baseline; control returns to baseline). Root-triggered permanent kernel resource leak, unbounded with repetition; not memory corruption. Fix validated: with vclean VFIFO-aware flags + clamped fifo_close decrements (fix.diff), the same PoC returns to baseline on rebuilt kernel #1 and normal fifo semantics are unaffected.
No comments yet.