[Bug report] ufs: fs_cgsize from the superblock indexes struct ufs_buffer_head.bh[8] unchecked - UBSAN out-of-bounds on the first allocation after remount-rw

From: Guikun Yang

Date: Sun Nov 01 2026 - 22:10:29 EST


To: linux-fsdevel@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx, Christian Brauner, Jan Kara
Subject: [Bug report] ufs: fs_cgsize from the superblock indexes
struct ufs_buffer_head.bh[8] unchecked - UBSAN out-of-bounds on the first
allocation after remount-rw

Hi,

While testing on-disk field corruption in the UFS driver, we found that
the superblock's fs_cgsize field is copied into uspi->s_cgsize without any
range check, and is then used as a block count that indexes the fixed-size
bh[UFS_MAXFRAG] array. A crafted value writes up to 120 pointers past the
end of that array (UBSAN array-index-out-of-bounds), panicking the kernel
on the first allocation after a read-write remount.

The code, fs/ufs/super.c:

uspi->s_cgsize = fs32_to_cpu(sb, usb1->fs_cgsize); /* no validation */

fs/ufs/cylinder.c:

UCPI_UBH(ucpi)->count = uspi->s_cgsize >> sb->s_blocksize_bits;
...
UCPI_UBH(ucpi)->bh[0] = sbi->s_ucg[cgno];
for (i = 1; i < UCPI_UBH(ucpi)->count; i++)
UCPI_UBH(ucpi)->bh[i] = sb_bread(sb, UCPI_UBH(ucpi)->fragment + i);

struct ufs_buffer_head.bh has UFS_MAXFRAG = UFS_BSIZE / UFS_FSIZE = 8
entries. With ufstype=44bsd the fragment size is 512 and the block size
set by sb_set_blocksize() is also 512, so a normal s_cgsize of 2048 gives
count = 4. A crafted s_cgsize of 0x10000 gives count = 128, i.e. 120
stores past bh[7], overwriting the fields that follow the array in struct
ufs_cg_private_info.

Trigger: a hand-built UFS1 image (44BSD layout; there is no mkfs.ufs on
Linux) with fs_cgsize changed from 2048 to 0x10000, and cylinder group
blocks whose cs_nbfree/cs_nifree are zeroed so the allocator reaches
cg_found:

mount -o ro -t ufs -o ufstype=44bsd /dev/vda /mnt (succeeds)
mount -o remount,rw /mnt
mkdir /mnt/d1 (first allocation)

UBSAN: array-index-out-of-bounds in fs/ufs/cylinder.c:50:21
index 8 is out of range for type 'buffer_head *[8]'
Call Trace:
__ubsan_handle_out_of_bounds
ufs_read_cylinder
ufs_load_cylinder
ufs_new_inode
ufs_mkdir
Kernel panic - not syncing: UBSAN: panic_on_warn set ...

Reproduced 3/3 on a self-built 7.3.0 kernel (CONFIG_UFS_FS=y,
CONFIG_UFS_FS_WRITE=y, CONFIG_UBSAN=y, CONFIG_UBSAN_BOUNDS=y) under
QEMU/KVM. A control image with fs_cgsize = 2048 runs the same sequence
without an out-of-bounds access (the mkdir then fails normally with
ENOSPC). The field and the missing bound are unchanged in current
mainline.

A natural fix is to validate s_cgsize when the superblock is read:
reject s_cgsize < s_fsize or (s_cgsize >> s_blocksize_bits) > UFS_MAXFRAG,
which also covers the zero case. Happy to test a patch.

We report this as a crafted-image robustness issue: the crash is a plain
UBSAN out-of-bounds violation reachable from a normal ro-mount,
remount-rw and a single mkdir.

The reproducer (image with the flipped field, a clean control image,
the run script, a guest init that mounts ro, remounts rw and mkdirs, and
the image builder with the field offsets) is available on request.

Kernel: self-built 7.3.0, CONFIG_UFS_FS=y, CONFIG_UFS_FS_WRITE=y,
CONFIG_UBSAN=y; QEMU/KVM x86_64, 1 vCPU.

Thanks,
Guikun Yang