mfsclose() deactivates the MFS I/O server on ANY device close β no finality check (speculative: would destroy_dev/kfree the serving mfsnode under a live mount if a non-final close ever slips through)
Summary
mfsclose() unconditionally clears mfs_active and wakes the server, assuming every close is the unmount-driven final close. devfs can invoke d_close for VRECLAIMED vnodes regardless of open count (devfs_vnops.c:1169-1171), and the close gate's opencount comes from count_dev(), which the kernel itself marks 'XXX NOT SMP SAFE' (:1102/:1148). A non-final close while mounted would kill the server, then destroy_dev (async) + immediate kfree(mfsp) leave the still-mounted fs issuing bios against a freed mfsnode (UAF). No reachable deterministic trigger demonstrated: FWRITE opens of a mounted mfs device return EBUSY and read-only opens migrate to the primary devfs vnode, so second-devfs vnode reclaims fire no d_close - three guest constructions all left the server alive (full honesty record; a stock panic initially attributed here was re-attributed to DF-3170). Residual exposure: the in-tree-acknowledged count_dev miscount or an equivalent opencount anomaly. Defense-in-depth guard built into validation kernels and proven harmless. Fix: mfsclose ignores closes while si_mountpoint != NULL.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3171 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.sh | β | 1.5 KB | view raw | |
| mfsrace.c | β | 2.8 KB | view raw | |
| run.log | β | 2.6 KB | view raw | |
| panic_investigated_misattribution.txt | β | 812 B | view raw | |
| boot_serial.log | β | 13.6 KB | view raw | |
| env.txt | β | 517 B | view raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| fix.diff | β | 948 B | view raw | |
| fix_build.log | β | 1.3 KB | view raw | |
| fix_run.log | β | 1.2 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-3171 β VERDICT
Status: not_reproduced (for the hypothesized trigger). The code hazard is real but no reachable trigger was demonstrable; see the honesty record below. Confidence: speculative. Impact: none demonstrated.
What was hypothesized
mfsclose() (mfs_vfsops.c:136-147) clears mfs_active unconditionally,
taking the I/O server down on ANY device close. devfs calls d_close on
VRECLAIMED vnodes regardless of the remaining open count
(devfs_vnops.c:1169-1171), so a non-final close (vnode reclaim while the
mount still holds the device) would kill the server under a live mount β
destroy_dev (async, devfs_core.c:754-763) + kfree(mfsp)
(mfs_vfsops.c:494-498) β still-mounted filesystem doing I/O through a
freed mfsnode (UAF).
What actually happened (honesty record)
- First stock attempt (~15:56):
exec 8<>/mnt/d2/mfs853+umount -f. The box panicked withBad tailq NEXT(...->tqh_last) != NULLfrom mfsstrategy β initially filed as this finding's reproduction. That attribution was wrong: the8<>(O_RDWR) open of a mounted-RW D_DISK device fails EBUSY in devfs_specopen (devfs_vnops.c:1006-1020), so no fd existed and no d_close ever fired. The panic was DF-3170's producer/consumer race, which the concurrently-running writers were exercising (full reanalysis in findings/poc/DF-3170/run.2.log β the panic evidence has been reattributed to DF-3170). - Second stock attempt (read-only open, fd held by a short-lived
sh -c): the fd closed when the shell exited before umount β vclean had no opencount to force-close. Server survived. - Third stock attempt (read-only fd held by a
sleep 90holder process acrossumount -f /mnt/d2): umount succeeded, server survived, mount healthy, I/O fine β because devfs device opens migrate to the device's PRIMARY vnode (devfs_specopen's orig_vp handling, devfs_vnops.c:956-1000 β "the returned ... might not be using the vp we supplied"), leaving the second-devfs vnode with zero open count; its reclaim fires no VOP_CLOSE, and the primary vnode (in the main /dev devfs) is never reclaimed while the system runs.
Why this is still filed (speculative, Low)
The hazard is one miscount away: devfs_vnops.c:1148 and :1102 compute the
close gate with count_dev(dev) and are annotated "XXX NOT SMP SAFE"
in-tree. A miscount that yields a false opencount == 1 on some close
while the mount holds another reference makes mfsclose fire early with
exactly the consequences hypothesized (server exit under a live mount,
destroy_dev + kfree of the serving mfsnode, bios on freed memory). The
in-tree comment at mfs_vfsops.c:120-121 ("We allow the underlying MFS block
device to be opened and read") makes stray opens an expected condition.
This reviewer could not win that race on demand in the time budget; no
reachable deterministic trigger exists via the devfs#2 construction.
Fix (defense-in-depth, validated harmless)
fix.diff: mfsclose() ignores closes while dev->si_mountpoint != NULL
(ffs_unmount clears it before the real final close, ffs_vfsops.c:851-855).
Validated on a patched in-guest kernel (#1 Mon Sep 7 16:11:15 2026 and
rebuilt #2 16:51:08):
- mount / I/O / normal unmount lifecycle fully intact (umount rc=0,
mount gone, server exited via EMOUNTEXIT, /dev/mfsN removed);
- read-only-fd + forced devfs umount construction: server survives, mount
continues serving I/O;
- 11+ minutes of DF-3170 stress (plus ENOSPC churn) across multiple
concurrent MFS mounts: no regression.
Since the bug itself never manifested, fix validation is recorded as
inconclusive (guard proven safe and lifecycle-preserving; bad behavior
absent on both stock and patched).
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
inconclusiveThe guarded behavior could not be shown bad-then-good because no construction manifested the bug on the stock kernel (baseline_reproduced=0 by absence of trigger, not by fix). The guard was proven safe: normal mount/IO/umount lifecycle intact on both patched builds, kill constructions ignored, 11+ min stress without regression.
findings/poc/DF-3171/fix_run.log, findings/poc/DF-3171/fix_build.log
Confirmed kernel references
Detail
Evidence (decisive lines)
['run.log (all three trigger attempts + stock normal-teardown control)', 'VERDICT.md (attribution retraction + remaining count_dev race rationale)', 'panic_investigated_misattribution.txt (the reattributed panic)', 'fix_run.log (guard validation matrix)']
PoC changes
Trigger revised three times while chasing the mechanism: FWRITE open -> read-only open with short-lived holder -> read-only open with persistent (sleep 90) holder across umount -f; each stage's outcome recorded; the open-vnode-migration discovery explains all negatives.
Verified recommended fix
mfsclose: ignore closes while dev->si_mountpoint != NULL (ffs_unmount clears it before the true final close).
Verdict
The mfsclose hazard is code-real (mfs_active cleared on any device close; devfs can call d_close on VRECLAIMED vnodes or on a count_dev() miscount the kernel itself marks 'XXX NOT SMP SAFE'), but no reachable trigger was demonstrable: FWRITE opens of a mounted mfs device return EBUSY (devfs_vnops.c:1006-1020), and read-only opens migrate to the device's primary devfs vnode so reclaiming a second-devfs vnode never fires d_close. Three trigger constructions on the stock QEMU kernel all left the server alive and the mount healthy. The stock panic initially attributed to this finding was reattributed to DF-3170 after tracing the EBUSY open failure (full honesty record in VERDICT.md/run.log). Filed as Low/speculative hardening; the si_mountpoint guard in mfsclose was built and validated as harmless (normal mount/IO/umount lifecycle fully intact on two patched kernels).
No comments yet.