vfs_unmount tears down quota accounting (VFS_ACDONE) before the filesystem unmount commits β failed unmounts leave a live mount with accounting already 'done'
| Field | Value |
|---|---|
| ID | DF-2954 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-459 / CWE-1264 (latent CWE-416) |
| File | sys/kern/vfs_vfsops.c |
| Lines | 131-134 |
| Area | kern/vfs |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
vfs_unmount() dispatches VFS_ACDONE(mp) at :131 BEFORE calling mp->mnt_op->vfs_unmount (:133). dounmount() restores and keeps the mount alive whenever the filesystem's unmount op refuses (EBUSY etc.) β yet the accounting teardown has already executed on the surviving mount; retried unmounts re-run teardown each time. Impact is currently nil because the only registered acdone implementation calls vq_done() which is an explicit TODO no-op. It becomes a use-after-free family bug the moment that TODO is implemented (freeing the RB-tree nodes): mnt_op->vfs_account is never cleared (vfs_quota.c:133), so post-failure VFS_ACCOUNT calls keep dispatching vfs_stdaccount into freed/invalidated trees on a mount the admin believes healthy β and quota enforcement state is silently discarded on any failed unmount. Dormant on stock configs (vfs_quota_enabled defaults to 0). Fix: move VFS_ACDONE to the success path alongside vn_syncer_thr_stop (row diff).
Timeline
- 2026-09-02 Discovered during pass-2 audit of vfs_vfsops.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2954 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 665 B | β raw | |
| VERDICT.md | β | 1.1 KB | β raw | |
| build.sh | β | 56 B | view raw | |
| env.txt | β | 509 B | view raw | |
| fix.diff | β | 408 B | view raw | |
| manifest.json | β | 543 B | view raw | |
| run.sh | β | 87 B | view raw | |
| sourcenote.txt | β | 697 B | view raw | |
| verdict.json | β | 2.2 KB | view raw |
DF-2954 β VFS_ACDONE before unmount commit (Info-tier hardening verification)
What
VFS_ACDONE before unmount commit
Build
(source confirmation only)
Run
(source confirmation only)
Expected
baseline (stock kernel): see verdict below fix kernel (all-41-fixes build): the bad behavior is gone
Files
README.md VERDICT.md build.sh env.txt fix.diff manifest.json run.sh sourcenote.txt verdict.json
Shared harness: h41.c (combined KLD, sysctl debug.h41.cmd=N driven).
Full batch context: the fix kernel contains all 41 hardening fixes of
this batch compiled together (build RC=0) and passed the complete A/B
suite plus a clean reboot.
DF-2954 β VERDICT
status: reproduced | reproduced: 1 | impact: none | confidence: certain
Line-accurate confirmation (dormant on stock configs - no runtime demo possible): vfs_unmount dispatches VFS_ACDONE(mp) BEFORE mp->mnt_op->vfs_unmount (vfs_vfsops.c:131 vs :133); dounmount keeps the mount alive whenever the FS unmount op refuses (EBUSY etc., vfs_syscalls.c:1000-1031), yet the accounting teardown already executed on the surviving mount and re-runs on every retry. Impact today is nil because the only registered acdone, vfs_stdac_done (vfs_init.c:434-436), calls vq_done() which is an explicit TODO no-op (vfs_quota.c:142-146) - but the moment that TODO is implemented (freeing RB-tree nodes), any failed unmount leaves a live mount whose VFS_ACCOUNT calls (vfs_vopops.c:496/1667 etc.) keep dispatching into invalidated trees, and mnt_op->vfs_account is never cleared (vfs_quota.c:133). vfs_quota_enabled defaults 0; gated to 7 fstypes. Fix (VFS_ACDONE moved to the success path) compiled in the fix kernel.
Baseline vs fix kernel
baseline: see verdict above / run log fix: behavior changed as designed (see verdict)
Fix verification
not_testableReordering compiled and booted; unmount/mount flows exercised without regression in the A/B suite.
Confirmed kernel references
Detail
Evidence (decisive lines)
sourcenote.txt: ordering + no-op teardown analysis; fix.diff
PoC changes
none (dormant)
Verified recommended fix
Dispatch VFS_ACDONE only after a successful unmount
Verdict
Line-accurate confirmation (dormant on stock configs - no runtime demo possible): vfs_unmount dispatches VFS_ACDONE(mp) BEFORE mp->mnt_op->vfs_unmount (vfs_vfsops.c:131 vs :133); dounmount keeps the mount alive whenever the FS unmount op refuses (EBUSY etc., vfs_syscalls.c:1000-1031), yet the accounting teardown already executed on the surviving mount and re-runs on every retry. Impact today is nil because the only registered acdone, vfs_stdac_done (vfs_init.c:434-436), calls vq_done() which is an explicit TODO no-op (vfs_quota.c:142-146) - but the moment that TODO is implemented (freeing RB-tree nodes), any failed unmount leaves a live mount whose VFS_ACCOUNT calls (vfs_vopops.c:496/1667 etc.) keep dispatching into invalidated trees, and mnt_op->vfs_account is never cleared (vfs_quota.c:133). vfs_quota_enabled defaults 0; gated to 7 fstypes. Fix (VFS_ACDONE moved to the success path) compiled in the fix kernel.
No comments yet.