[BUG] ext4: stack OOB in set_flexbg_block_bitmap() via EXT4_IOC_GROUP_ADD

From: MarkLee131

Date: Thu Sep 17 2026 - 11:36:58 EST


From: Kaixuan Li <kaixuanli0131@xxxxxxxxx>

EXT4_IOC_GROUP_ADD can make set_flexbg_block_bitmap() index two one-element
arrays at offset 1. KASAN reports a stack out-of-bounds read on v6.12.9 and
v7.2.4; a reproducer is below.

EXT4_IOC_GROUP_ADD requires CAP_SYS_RESOURCE, so this goes to the list
rather than security@xxxxxxxxxx. The ioctl returns 0 whether or not the
read happens.

ext4_group_add() builds a one-element flex group descriptor whose arrays
are locals in its own frame:

__u16 bg_flags = 0;
...
flex_gd.count = 1;
flex_gd.groups = input; /* the ioctl's single struct */
flex_gd.bg_flags = &bg_flags; /* one __u16 */
err = ext4_flex_group_add(sb, inode, &flex_gd);

set_flexbg_block_bitmap() derives its index from a block number, not from
flex_gd->count (fs/ext4/resize.c):

group = ext4_get_group_number(sb, EXT4_C2B(sbi, first_cluster));
start = EXT4_B2C(sbi, ext4_group_first_block_no(sb, group));
group -= flex_gd->groups[0].group;
...
if (flex_gd->bg_flags[group] & EXT4_BG_BLOCK_UNINIT) {
BUG_ON(flex_gd->count > 1);
continue;
}
...
bh = sb_getblk(sb, flex_gd->groups[group].block_bitmap);

The BUG_ON tests flex_gd->count, not group, so it does not fire.

verify_group_input() bounds the three supplied blocks against
[start, start + input->blocks_count), but nothing bounds blocks_count
against EXT4_BLOCKS_PER_GROUP(sb). A blocks_count spanning more than one
group lets inode_table sit in group N+1, so group above becomes 1 while
both arrays hold one element.

The second access feeds a journalled write: the out-of-bounds 8 bytes
become the block number passed to sb_getblk(),
ext4_journal_get_write_access(), mb_set_bits() and
ext4_handle_dirty_metadata().

ext4_resize_fs() is not affected. ext4_setup_next_flex_gd() sets
blocks_count = EXT4_BLOCKS_PER_GROUP(sb) and ext4_alloc_group_tables()
keeps the tables inside the flex group, so group stays below flex_gd->count
there. Only EXT4_IOC_GROUP_ADD takes these numbers from userspace.

Reproducer
==========

blocks_count must be first_data_block + N * blocks_per_group, or
verify_group_input() stops at "Last group not full" first:

truncate -s 64M img
mke2fs -q -F -t ext4 -b 1024 -g 1024 -O ^has_journal img 4097
mount -o loop img /mnt

Group 4 is the next group and carries no superblock backup, so overhead is
0. inode_table is the first block of group 5, which makes the index 1:

struct ext4_new_group_input in = {
.group = 4,
.block_bitmap = 4097,
.inode_bitmap = 4098,
.inode_table = 5121,
.blocks_count = 1088,
.reserved_blocks = 0,
};
ioctl(fd_on_mnt, EXT4_IOC_GROUP_ADD, &in); /* returns 0 */

KASAN
=====

v7.2.4, x86_64 defconfig plus CONFIG_KASAN=y CONFIG_KASAN_STACK=y
CONFIG_KASAN_INLINE=y, under QEMU:

BUG: KASAN: stack-out-of-bounds in set_flexbg_block_bitmap+0x531/0x6a0
Read of size 2 at addr ffff8880072b7b2a by task init/1
CPU: 0 UID: 0 PID: 1 Comm: init Not tainted 7.2.4 #1 PREEMPT(lazy)
Call Trace:
<TASK>
set_flexbg_block_bitmap+0x531/0x6a0
ext4_flex_group_add+0x3885/0x5730
ext4_group_add+0xc13/0x14b0
ext4_ioctl_group_add+0x1a4/0x4a0

BUG: KASAN: stack-out-of-bounds in set_flexbg_block_bitmap+0x646/0x6a0
Read of size 8 at addr ffff8880072b7da8 by task init/1

Size 2 is bg_flags, size 8 is groups[1].block_bitmap. v6.12.9 gives the
same pair. set_flexbg_block_bitmap() and verify_group_input() are unchanged
in current master.

set_flexbg_block_bitmap() has computed the index this way since
33afdcc5402d ("ext4: add a function which sets up group blocks of a flex
bg"), v3.3.

Fix
===

Either bounds check works, and I do not know which you prefer:

- reject blocks_count > EXT4_BLOCKS_PER_GROUP(sb) in
verify_group_input(), which is what the single-group path means; or

- bound group against flex_gd->count in set_flexbg_block_bitmap(), which
also covers a future multi-group caller.

I can send a patch for either, and test one.

Kaixuan