[PATCH] jfs: fix double free of the log superblock buffer in lmLogInit()
From: Aleksandr Khromov
Date: Mon Aug 17 2026 - 03:18:29 EST
lbmIOWait() called with the lbmFREE flag returns the buffer to the log
buffer free list unconditionally, before it reports the outcome of the
i/o. lmLogInit() ignores that and, when the write of the log superblock
fails, jumps to errout30, which falls through to errout20 and calls
lbmFree(bpsuper) a second time.
The second lbmFree() inserts the buffer into the singly linked free list
twice, which makes the list circular. lbmLogShutdown(), called right
after on the same error path, walks that list until it hits NULL, so it
never terminates: the mounting task ends up freeing the same lbufs and
their pages over and over in an endless loop inside the kernel - a
repeated double free, with the usual consequences once the freed memory
is handed out to someone else.
The write of the log superblock is the very first write to the device
after the log is opened, so no fault injection is needed to reach the
failure: mounting a crafted image whose s_logpxd points outside the
device is enough.
KASAN, mounting a crafted image on a loop device (offsets decoded with
scripts/decode_stacktrace.sh):
metapage_write_end_io: I/O error
BUG: KASAN: double-free in lmLogInit (fs/jfs/jfs_logmgr.c:1862 fs/jfs/jfs_logmgr.c:1413)
Free of addr ffff8881027c8a00 by task init/71
CPU: 1 UID: 0 PID: 71 Comm: init Tainted: G B 7.2.0-rc7-g2f1baf1fc892 #2 PREEMPT(lazy)
Tainted: [B]=BAD_PAGE
Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
kfree (mm/slub.c:6692)
lmLogInit (fs/jfs/jfs_logmgr.c:1862 fs/jfs/jfs_logmgr.c:1413)
lmLogOpen (fs/jfs/jfs_logmgr.c:1173 fs/jfs/jfs_logmgr.c:1067)
jfs_mount_rw (fs/jfs/jfs_mount.c:257)
jfs_fill_super (fs/jfs/super.c:533)
get_tree_bdev_flags (fs/super.c:1640)
vfs_get_tree (fs/super.c:1700)
path_mount (fs/namespace.c:4161)
__x64_sys_mount (fs/namespace.c:4367)
Freed by task 71:
kfree (mm/slub.c:6692)
lmLogInit (fs/jfs/jfs_logmgr.c:1862 fs/jfs/jfs_logmgr.c:1413)
lmLogOpen (fs/jfs/jfs_logmgr.c:1173 fs/jfs/jfs_logmgr.c:1067)
jfs_mount_rw (fs/jfs/jfs_mount.c:257)
jfs_logmgr.c:1413 is the errout10 lbmLogShutdown() call and :1862 is its
kfree(lbuf) - the shutdown loop walks the free list that the second
lbmFree() made circular, so it keeps re-freeing the same lbufs; KASAN
also flags the accompanying slab-use-after-free reads of the freed
l_freelist on every extra pass.
Clear bpsuper once lbmIOWait() has disposed of it and make errout20 skip
the release in that case.
Found by Linux Verification Center (linuxtesting.org).
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Aleksandr Khromov <haa@xxxxxxxxx>
---
fs/jfs/jfs_logmgr.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/fs/jfs/jfs_logmgr.c b/fs/jfs/jfs_logmgr.c
index 695415cbfe98..3277701d306d 100644
--- a/fs/jfs/jfs_logmgr.c
+++ b/fs/jfs/jfs_logmgr.c
@@ -1242,7 +1242,7 @@ int lmLogInit(struct jfs_log * log)
int rc = 0;
struct lrd lrd;
struct logsuper *logsuper;
- struct lbuf *bpsuper;
+ struct lbuf *bpsuper = NULL;
struct lbuf *bp;
struct logpage *lp;
int lsn = 0;
@@ -1380,7 +1380,13 @@ int lmLogInit(struct jfs_log * log)
log->serial = le32_to_cpu(logsuper->serial) + 1;
logsuper->serial = cpu_to_le32(log->serial);
lbmDirectWrite(log, bpsuper, lbmWRITE | lbmRELEASE | lbmSYNC);
- if ((rc = lbmIOWait(bpsuper, lbmFREE)))
+ rc = lbmIOWait(bpsuper, lbmFREE);
+ /*
+ * lbmIOWait() with lbmFREE has already returned bpsuper
+ * to the free list, whatever the outcome of the i/o.
+ */
+ bpsuper = NULL;
+ if (rc)
goto errout30;
}
@@ -1410,7 +1416,8 @@ int lmLogInit(struct jfs_log * log)
lbmFree(bp);
errout20: /* release log superblock */
- lbmFree(bpsuper);
+ if (bpsuper)
+ lbmFree(bpsuper);
errout10: /* unwind lbmLogInit() */
lbmLogShutdown(log);
--
2.48.1