[BUG REPORT] xfs: log recovery dereferences an unvalidated ilf_boffset - KASAN use-after-free / page fault at mount time
From: Guikun Yang
Date: Sun Nov 01 2026 - 08:42:59 EST
Hi,
We found that xlog_recover_inode_commit_pass2() uses the on-disk
ilf_boffset field to index into the inode cluster buffer without any
bounds validation, and we can drive a KASAN use-after-free or an
unrecoverable page fault at mount time with a crafted log record.
We are aware of the RFC series "[PATCH 00/48] Validate journal log
items during log recovery" (2026-10-13), whose 35/48 validates the
inode item format header via xfs_imap()-derived boffset/blkno. This
report is not a proposal to fix it differently: it provides an
independent reproducer plus KASAN traces for the vulnerability as it
stands in mainline today (7.3.0 and current master both still carry
the unvalidated dereference), which may be useful as a test case for
the series.
The code, current mainline (fs/xfs/xfs_inode_item_recover.c:356):
error =3D xfs_buf_read(mp->m_ddev_targp, in_f->ilf_blkno, in_f->ilf_len,
0, &bp, &xfs_inode_buf_ops);
if (error)
goto error;
ASSERT(in_f->ilf_fields & XFS_ILOG_CORE);
dip =3D xfs_buf_offset(bp, in_f->ilf_boffset); /* no check */
if (XFS_IS_CORRUPT(mp, !xfs_verify_magic16(bp, dip->di_magic)))
xfs_buf_offset() is bp->b_addr + offset with no check. ilf_boffset is
an int32_t read straight from the log item format region
(libxfs/xfs_log_format.h:301). The only writers are the in-memory
im_boffset copy and the 32-bit format converter, which copies the
value verbatim. On the read side we could not find any validation:
the magic check above dereferences the resulting pointer, and on
v3 inodes dip->di_lsn / di_version / di_flushiter are read next.
xfs_log_dinode_to_disk() then writes a full log dinode through the
same pointer if the checks happen to pass. On a CONFIG_XFS_DEBUG=3Dn
kernel the ASSERT()s compile away entirely.
The buffer item path in the same recovery machinery had this class
of bug fixed in July (813f8136a2ce, "xfs: bounds-check buffer log
item's dirty bitmap"), which added the comment at
xfs_buf_item_recover.c:490-497 stating the logged fields are
attacker-controlled and that a real check is needed rather than an
ASSERT(). ilf_boffset is the same pattern in the inode item path and
is, as of today, still unprotected in mainline.
Trigger (no special mount options):
1. mount an XFS filesystem, create a couple of files, crash the
machine (sysrq-c) without unmounting, so the log holds inode log
items;
2. edit one inode log item's ilf_boffset in the log area to a wild
value (the record CRC must be recomputed; CRC only protects
against accidental corruption);
3. mount the image again. Mount-time recovery replays the inode
item and dereferences b_addr + ilf_boffset.
With ilf_boffset =3D 0x10000000 on a 64MB image (KASAN kernel, 7.3.0):
BUG: KASAN: use-after-free in xlog_recover_inode_commit_pass2+0x1187/0x1f=
70
Read of size 2 at addr ffff888014914000 by task init/1
CPU: 0 UID: 0 PID: 1 Comm: init Not tainted 7.3.0 #23
Call Trace:
xlog_recover_inode_commit_pass2+0x1187/0x1f70
xlog_recover_items_pass2+0x105/0x280
xlog_recover_commit_trans+0x7e6/0x9d0
xlog_recovery_process_trans+0x1a1/0x1c0
xlog_recover_process_ophdr+0x1ea/0x400
xlog_recover_process_data+0x12d/0x3c0
xlog_recover_process+0x10d/0x390
xlog_do_recovery_pass+0x8cf/0xd40
xlog_do_log_recovery+0x89/0xb0
xlog_do_recover+0x78/0x410
xlog_recover+0x2d0/0x4e0
xfs_log_mount+0x23a/0x480
xfs_mountfs+0xfe7/0x20b0
xfs_fs_fill_super+0x15dc/0x1e00
get_tree_bdev_flags+0x36b/0x5f0
Kernel panic - not syncing: KASAN: panic_on_warn set ...
With ilf_boffset =3D 0x40000000 instead:
BUG: unable to handle page fault for address: ffffed10088a2800
Oops: Oops: 0000 [#1] SMP KASAN PTI
Kernel panic - not syncing: Fatal exception
With the original value the same log replays cleanly and the
filesystem mounts; the behavior difference is solely the unchecked
offset. Deterministic, reproduced 3/3 on a self-built 7.3.0 kernel
(CONFIG_KASAN_GENERIC=3Dy, CONFIG_XFS_FS=3Dy, CONFIG_XFS_DEBUG=3Dn).
Two sibling fields in log-replay formats share the shape (offset or
count from disk feeding a buffer access with only an ASSERT, or
nothing): qlf_boffset applied via xfs_buf_offset() at
xfs_dquot_item_recover.c:135, and bb_numrecs used as the memcpy count
in xfs_bmbt_to_bmdr() (libxfs/xfs_bmap_btree.c:174-176). We mention
them as context only; we expect the RFC series covers them as part of
"every log item type".
ilf_boffset has been unvalidated since it was introduced:
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
A natural fix mirrors 813f8136a2ce: validate that ilf_boffset lies
within the buffer that was just read (bp->b_length) with
XFS_IS_CORRUPT() and fail recovery with -EFSCORRUPTED. The RFC 35/48
approach (derive from ilf_ino via xfs_imap() and require a match) is
stronger and we are happy to test the series against our reproducer
if that is useful.
We frame this the same way 813f8136a2ce was framed: the input is the
log itself, which recovery must already treat as untrusted on
unclean mounts; we are not claiming a privilege-boundary crossing
beyond "whoever controls the device image controls the log".
The reproducer bundle (image builder + log patcher + CRC
recomputation matching xlog_cksum(), guest init, run script, and a
pre-baked dirty image) is available on request.
Environment: QEMU/KVM x86_64, 1 vCPU, kernel 7.3.0 self-built with
CONFIG_KASAN_GENERIC=3Dy, CONFIG_UBSAN=3Dy, CONFIG_XFS_FS=3Dy,
CONFIG_XFS_DEBUG=3Dn.
Thanks,
Guikun Yang