journal_remove_all_journals never detaches: mnt_jbitmap (1024 bytes, M_JOURNAL) leaks on every journaled unmount
| Field | Value |
|---|---|
| ID | DF-2766 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-401 Missing Release of Memory |
| File | sys/kern/vfs_jops.c |
| Lines | 423-430 (only free at :251) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
The MOUNTCTL_REMOVE path calls journal_detach() when mnt_jlist drains, but journal_remove_all_journals() β invoked from dounmount β only destroys journals and never detaches. mnt_jbitmap's only free is journal_detach; the mount destructor does not touch it. Guest-proven: vmstat -m 'journal' grows to exactly 5 allocs / 5.00K after 5 mount+mountctl-a+umount cycles while 'journal-fifo' returns to 0 β exactly 5Γ1024 leaked bitmaps; unbounded across mount-loop workloads. Root-gated resource leak.
Recommended fix
journal_remove_all_journals: after the drain loop, if
(mp->mnt_vn_journal_ops) journal_detach(mp); (safe/idempotent with
the DF-2763 fix; dounmount already holds mnt_token) β validated
(vmstat returns to 0).
Timeline
- 2026-08-30 Discovered during pass-2 audit of vfs_jops.c (GLM 5.3); leak proven + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2766 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 1.2 KB | β raw | |
| df2766_run.sh | β | 677 B | view raw | |
| run.log | β | 467 B | view raw | |
| run_fixed.log | β | 467 B | view raw | |
| fix.diff | β | 3.6 KB | view raw | |
| verdict.json | β | 1.7 KB | view raw |
DF-2766 β journal_remove_all_journals() never detaches: mnt_jbitmap (1024 B) leaks on every journaled unmount
What
journal_remove_all_journals() (sys/kern/vfs_jops.c:422-430) β called
from dounmount() (sys/kern/vfs_syscalls.c:1037) after VFS_UNMOUNT β
destroys every journal but, unlike the MOUNTCTL_REMOVE_VFS_JOURNAL
path in journal_mountctl() (:208-209), never calls
journal_detach(). mnt_jbitmap (JREC_STREAMID_JMAX/8 = 1024 bytes,
M_JOURNAL, allocated in journal_attach() :240) is therefore leaked
when a journaled mount is unmounted. It is the only code that frees
the bitmap besides journal_detach() (:251), and nothing in the mount
destruction path (vfs_mount.c:403 kfree(mp)) touches it β 1024 bytes
per journaled mount/unmount cycle, unbounded for mount-loop workloads
(synth, poudriere-style chroots with journaling).
PoC (reproduced, stock kernel)
vmstat -m | grep -w journal # baseline: 0 in use
for i in 1..5:
mount -t tmpfs tmpfs /mnt/leak
mountctl -a -w /root/jl_$i.bin /mnt/leak:L$i
umount /mnt/leak
vmstat -m | grep -w journal # 5 allocs / 5.00K in use
Observed: journal 5 5.00K after 5 cycles (5 x 1024 B), while
journal-fifo returned to 0 (the FIFO membase IS freed by
journal_destroy) β the delta is exactly the leaked bitmaps.
See run.log.
Fix
Call journal_detach() from journal_remove_all_journals() when the
list drains (dounmount already holds mnt_token; journal_detach is made
idempotent by the DF-2763 fix). See fix.diff.
DF-2766 β VERDICT
Status: reproduced (exact accounting match), fix validated.
Baseline (stock kernel)
5 x (mount tmpfs; mountctl -a; umount) cycles:
journal 0 0 -> 5 5.00K (in-use after 5 cycles) journal-fifo 0 -> 0 (membase properly freed)
5.00K = 5 x 1024 B = JREC_STREAMID_JMAX/8 = the leaked mnt_jbitmap blocks (M_JOURNAL). Only journal_detach() (vfs_jops.c:251) frees them, and journal_remove_all_journals() (vfs_jops.c:422-430, called from dounmount vfs_syscalls.c:1037) never calls it β unlike the MOUNTCTL_REMOVE path (:208-209). Nothing in the mount destructor (vfs_mount.c:403 kfree(mp)) touches mnt_jbitmap. run.log.
Fix (validated on kernel #2)
journal_remove_all_journals() now calls journal_detach() when the list
drains (fix.diff hunk 3; journal_detach made idempotent by the DF-2763
hunks; dounmount already holds mnt_token). Post-fix, the same 5-cycle
run leaves journal in-use at 0 (alloc count 255->265, all freed).
run_fixed.log.
Impact
1 KB leak per journaled mount/unmount cycle, unbounded across mount-loop workloads. Root-gated (journal install), no memory-safety consequence. Severity Low (CWE-401).
Fix verification
fixedsame 5-cycle run on patched kernel: journal in-use 0 (vs 5.00K baseline)
['run_fixed.log']
Confirmed kernel references
Detail
Evidence (decisive lines)
['run.log (0 -> 5 / 5.00K vs journal-fifo 0)', 'run_fixed.log (in-use stays 0 after fix)']
PoC changes
authored from scratch; vmstat -m malloc-type accounting as the oracle
Verified recommended fix
journal_remove_all_journals: call journal_detach() when mnt_jlist drains (fix.diff hunk 3)
Verdict
journal_remove_all_journals() (vfs_jops.c:422-430, from dounmount vfs_syscalls.c:1037) destroys the journals but never calls journal_detach(), and mnt_jbitmap (1024 B, M_JOURNAL) has no other freeing path on the unmount route - vmstat -m shows exactly 5.00K leaked after 5 journaled mount/unmount cycles (journal-fifo returns to 0, proving the destroy path otherwise frees). Fix (detach on drain) validated: in-use stays 0 over the same 5 cycles.
No comments yet.