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

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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2766 Β· 7 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

same 5-cycle run on patched kernel: journal in-use 0 (vs 5.00K baseline)

['run_fixed.log']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #2: Mon Aug 31 23:12:27 UTC 2026

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.