[PATCH] jfs: validate external log superblock fields in lmLogInit()
From: yangguikun
Date: Fri Oct 16 2026 - 05:28:48 EST
lmLogInit() validates only the magic, state and uuid of the on-disk log
superblock before copying its size and l2bsize fields into the in-memory
log structure, and it overwrites the sane l2bsize value set up for the
external-log case a few lines earlier:
fs/jfs/jfs_logmgr.c:1271
if (!test_bit(log_INLINELOG, &log->flag))
log->l2bsize = L2LOGPSIZE;
...
} else {
if (!uuid_equal(&logsuper->uuid, &log->uuid)) { ... }
log->size = le32_to_cpu(logsuper->size);
log->l2bsize = le32_to_cpu(logsuper->l2bsize);
l2bsize is then used directly as a shift count:
fs/jfs/jfs_logmgr.c:1892
bp->l_blkno = log->base + (pn << (L2LOGPSIZE - log->l2bsize));
fs/jfs/jfs_logmgr.c:1973
bio->bi_iter.bi_sector = bp->l_blkno << (log->l2bsize - 9);
fs/jfs/jfs_logmgr.c:2018, :2088
log->base + (bp->l_pn << (L2LOGPSIZE - log->l2bsize));
and size feeds log->logsize:
fs/jfs/jfs_logmgr.c:1385
log->logsize = (log->size - 2) << L2LOGPSIZE;
A crafted external log with l2bsize = 13 makes "L2LOGPSIZE - l2bsize"
negative and "l2bsize - 9" 4, so the computed block number and sector are
garbage. Mounting such an image panics deterministically:
UBSAN: shift-out-of-bounds in fs/jfs/jfs_logmgr.c:1892:32
shift exponent -1 is negative
UBSAN: shift-out-of-bounds in fs/jfs/jfs_logmgr.c:2018:28
UBSAN: shift-out-of-bounds in fs/jfs/jfs_logmgr.c:2088:28
BUG: KASAN: slab-use-after-free in lmLogInit+0x16dd/0x1a90
Read of size 8 at addr ff110000413e4218 by task mount/8208
...
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:___free_pages+0x15b/0x190
Kernel panic - not syncing: Fatal exception
Three runs on three fresh VMs, all panicked; a control image differing
only in l2bsize = 12 mounts with no UBSAN and no KASAN report.
The inline-log branch of the same function already validates its field
(jfs_logmgr.c:1307 compares the on-disk size against the in-memory one),
and jfs_mount() already validates sbi->l2bsize against the block size
(jfs_mount.c:366). The external-log branch is the only path that trusts
the on-disk values unchecked. Validate l2bsize against L2LOGPSIZE, which
is what the external-log path sets it to, and reject a size too small for
logsize to be meaningful.
Found by inspection; the reproducer is a crafted image mounted through a
loop device.
Signed-off-by: yangguikun <yangguikun19@xxxxxx>
---
fs/jfs/jfs_logmgr.c | 11 +++++++++++
1 file changed, 11 insertions(+)
--- a/fs/jfs/jfs_logmgr.c
+++ b/fs/jfs/jfs_logmgr.c
@@ -1316,6 +1316,17 @@
rc = -EINVAL;
goto errout20;
}
+ /*
+ * The external log must use the same block size as the
+ * file system. log->l2bsize was set to L2LOGPSIZE above
+ * and is used as a shift count below.
+ */
+ if (le32_to_cpu(logsuper->l2bsize) != L2LOGPSIZE ||
+ le32_to_cpu(logsuper->size) < 2) {
+ jfs_warn("invalid log superblock");
+ rc = -EINVAL;
+ goto errout20;
+ }
log->size = le32_to_cpu(logsuper->size);
log->l2bsize = le32_to_cpu(logsuper->l2bsize);
jfs_info("lmLogInit: external log:0x%p base:0x%Lx size:0x%x",