[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