Re: xfs: XLOG_WAS_CONT_TRANS on a transaction with no buffered regions reads ri_buf[-1] - null deref Oops during mount log recovery

From: Guikun Yang

Date: Sat Nov 07 2026 - 05:44:33 EST


Hi,

While continuing on the same recovery path I found a second issue in
the log-replay handling of the primary superblock buffer - this one is
an unvalidated install of logged geometry, and it is a different
mechanism from the flag-sequence gap reported above.

## 5. Recovery installs the logged superblock without validating its
geometry fields

xlog_recover_do_primary_sb_buffer() (fs/xfs/xfs_buf_item_recover.c)
recovers the buffer, then installs the on-disk superblock into the
in-core one with no validation:

error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn);
...
xfs_sb_from_disk(&mp->m_sb, dsb);

The only checks afterwards are that the AG count and rtgroup count did
not shrink. Everything else in the freshly installed superblock is
used as-is. The immediately following consumers trust the geometry:

error = xfs_update_last_ag_size(mp, orig_agcount);

which reaches __xfs_agino_range() (fs/xfs/libxfs/xfs_ag.c:172) and
shifts by the freshly installed sb_inopblog:

UBSAN: shift-out-of-bounds in fs/xfs/libxfs/xfs_ag.c:172:11
shift exponent 40 is too large for 32-bit type 'unsigned int'
__xfs_agino_range.cold
xfs_update_last_ag_size
xlog_recover_buf_commit_pass2
Kernel panic - not syncing: UBSAN: panic_on_warn set ...

The same mount-time read path validates these fields through
xfs_sb_read_verify()/xfs_validate_sb(); the recovery path bypasses
that entirely - the same on-disk structure gets two different levels
of trust depending on which path reads it.

## Reproducer

The base image carries a real dirty log from a normal crash. The
logged primary-superblock buffer item (XFS_LI_BUF, blf_blkno == 0,
XFS_BLFT_SB_BUF) is located, one byte in its data region is changed -
sb_inopblog, data offset 123, from 3 to 40 - and the v2 log record CRC
is recomputed. The unmodified image recovers and mounts cleanly.

Reproduced 3/3 with panic_on_warn=1. The code in mainline as of
2026-11-07 is identical.

Other geometry fields are installed through the same unvalidated
xfs_sb_from_disk() call and feed the same consumers: sb_agblocks,
sb_dblocks and sb_agcount go into __xfs_ag_block_count(),
sb_inopblock into the inode geometry, sb_blocklog into every shift and
mask derived from it. I did not chase each variant; the mechanism is
the single unvalidated install.

## Suggested fix

Validate the recovered superblock before installing it - the existing
xfs_validate_sb() (or at least its geometry subset) covers exactly the
fields these consumers use:

xfs_sb_from_disk(&mp->m_sb, dsb);
+ error = xfs_validate_sb(mp, &mp->m_sb, true);
+ if (error) { ... return -EFSCORRUPTED; }

The reproducer (log scanner that finds the SB buffer item, flips
sb_inopblog and recomputes the CRC, ready-made image, serial log) is
available on request.

Thanks,
Guikun Yang