[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