[lvc-project] [PATCH] ext2: fix use-after-free in read_block_bitmap()

Denis Zubov d.zubov at tssltd.ru
Tue May 12 14:47:37 MSK 2026


A crafted ext2 image can trigger two crashes via mkdir on a freshly
mounted filesystem:

WARN in ext2_get_group_desc() reached from ext2_free_blocks() via
the truncate path on inode eviction. block_group is computed from
a corrupt on-disk block pointer and exceeds sbi->s_groups_count.
With panic_on_warn set this panics the kernel.

KASAN use-after-free in ext2_try_to_allocate() when writing a bit
via ext2_set_bit_atomic(). read_block_bitmap() trusted
desc->bg_block_bitmap and called sb_getblk() on a block outside
the filesystem. The returned bh had b_data on a bdev-cache page
that was later reclaimed back to the buddy allocator.

bh_read() can return success while leaving the buffer not uptodate
on some failure paths, after which b_data is unreliable.

Found by Linux Verification Center (linuxtesting.org) with Syzkaller.

Fixes:  <8b91582500ae> ("ext2: retry block allocation if new blocks are allocated from system zone")
Signed-off-by: Denis Zubov <d.zubov at tssltd.ru>
---
 fs/ext2/balloc.c | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/fs/ext2/balloc.c b/fs/ext2/balloc.c
index b8cfab8f98b9..90390ca44c62 100644
--- a/fs/ext2/balloc.c
+++ b/fs/ext2/balloc.c
@@ -128,6 +128,7 @@ static int ext2_valid_block_bitmap(struct super_block *sb,
 static struct buffer_head *
 read_block_bitmap(struct super_block *sb, unsigned int block_group)
 {
+	struct ext2_sb_info *sbi = EXT2_SB(sb);
 	struct ext2_group_desc * desc;
 	struct buffer_head * bh = NULL;
 	ext2_fsblk_t bitmap_blk;
@@ -137,6 +138,24 @@ read_block_bitmap(struct super_block *sb, unsigned int block_group)
 	if (!desc)
 		return NULL;
 	bitmap_blk = le32_to_cpu(desc->bg_block_bitmap);
+
+	/*
+	 * Refuse to read a bitmap that lives outside the filesystem.
+	 * sb_getblk() on such a block can return a bh whose b_data
+	 * points to a bdev-cache page that is later reclaimed back to
+	 * the buddy allocator, leading to UAF in ext2_try_to_allocate
+	 * (KASAN: bad access at bitmap_bh->b_data inside
+	 * ext2_set_bit_atomic()).
+	 */
+	if (bitmap_blk < le32_to_cpu(sbi->s_es->s_first_data_block) ||
+	    bitmap_blk >= le32_to_cpu(sbi->s_es->s_blocks_count)) {
+		ext2_error(sb, __func__,
+				"Invalid block_bitmap = %llu for group %u (s_blocks_count = %u)",
+				(unsigned long long)bitmap_blk, block_group,
+				le32_to_cpu(sbi->s_es->s_blocks_count));
+		return NULL;
+	}
+
 	bh = sb_getblk(sb, bitmap_blk);
 	if (unlikely(!bh)) {
 		ext2_error(sb, __func__,
@@ -156,6 +175,20 @@ read_block_bitmap(struct super_block *sb, unsigned int block_group)
 			    block_group, le32_to_cpu(desc->bg_block_bitmap));
 		return NULL;
 	}
+	/*
+	 * bh_read could return 0 ("already in cache") or 1 ("read OK"),
+	 * but the buffer may still be !uptodate if the underlying I/O
+	 * silently failed (e.g. bg_block_bitmap points past end of device).
+	 * In that case b_data is unreliable and using it leads to UAF/OOB
+	 * in ext2_try_to_allocate / ext2_free_blocks.
+	 */
+	if (!buffer_uptodate(bh)) {
+		brelse(bh);
+		ext2_error(sb, __func__,
+			"Block bitmap not uptodate - block_group = %d, block_bitmap = %u",
+			block_group, le32_to_cpu(desc->bg_block_bitmap));
+		return NULL;
+	}
 
 	ext2_valid_block_bitmap(sb, desc, block_group, bh);
 	/*
@@ -509,6 +542,14 @@ void ext2_free_blocks(struct inode * inode, ext2_fsblk_t block,
 		      EXT2_BLOCKS_PER_GROUP(sb);
 	bit = (block - le32_to_cpu(es->s_first_data_block)) %
 		      EXT2_BLOCKS_PER_GROUP(sb);
+
+	if (block_group >= sbi->s_groups_count) {
+		ext2_error(sb, "ext2_free_blocks",
+			"Freeing blocks in non-existent group %lu (groups_count = %lu): block = %llu, count = %lu",
+			block_group, sbi->s_groups_count,
+			(unsigned long long)block, count);
+		goto error_return;
+	}
 	/*
 	 * Check to see if we are freeing blocks across a group
 	 * boundary.
-- 
2.53.0




More information about the lvc-project mailing list