Skip to content

Commit 1e1c2b8

Browse files
Lukas Czernertytso
authored andcommitted
ext4: block range must be validated before use in ext4_mb_clear_bb()
Block range to free is validated in ext4_free_blocks() using ext4_inode_block_valid() and then it's passed to ext4_mb_clear_bb(). However in some situations on bigalloc file system the range might be adjusted after the validation in ext4_free_blocks() which can lead to troubles on corrupted file systems such as one found by syzkaller that resulted in the following BUG kernel BUG at fs/ext4/ext4.h:3319! PREEMPT SMP NOPTI CPU: 28 PID: 4243 Comm: repro Kdump: loaded Not tainted 5.19.0-rc6+ #1 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.15.0-1.fc35 04/01/2014 RIP: 0010:ext4_free_blocks+0x95e/0xa90 Call Trace: <TASK> ? lock_timer_base+0x61/0x80 ? __es_remove_extent+0x5a/0x760 ? __mod_timer+0x256/0x380 ? ext4_ind_truncate_ensure_credits+0x90/0x220 ext4_clear_blocks+0x107/0x1b0 ext4_free_data+0x15b/0x170 ext4_ind_truncate+0x214/0x2c0 ? _raw_spin_unlock+0x15/0x30 ? ext4_discard_preallocations+0x15a/0x410 ? ext4_journal_check_start+0xe/0x90 ? __ext4_journal_start_sb+0x2f/0x110 ext4_truncate+0x1b5/0x460 ? __ext4_journal_start_sb+0x2f/0x110 ext4_evict_inode+0x2b4/0x6f0 evict+0xd0/0x1d0 ext4_enable_quotas+0x11f/0x1f0 ext4_orphan_cleanup+0x3de/0x430 ? proc_create_seq_private+0x43/0x50 ext4_fill_super+0x295f/0x3ae0 ? snprintf+0x39/0x40 ? sget_fc+0x19c/0x330 ? ext4_reconfigure+0x850/0x850 get_tree_bdev+0x16d/0x260 vfs_get_tree+0x25/0xb0 path_mount+0x431/0xa70 __x64_sys_mount+0xe2/0x120 do_syscall_64+0x5b/0x80 ? do_user_addr_fault+0x1e2/0x670 ? exc_page_fault+0x70/0x170 entry_SYSCALL_64_after_hwframe+0x46/0xb0 RIP: 0033:0x7fdf4e512ace Fix it by making sure that the block range is properly validated before used every time it changes in ext4_free_blocks() or ext4_mb_clear_bb(). Link: https://syzkaller.appspot.com/bug?id=5266d464285a03cee9dbfda7d2452a72c3c2ae7c Reported-by: [email protected] Signed-off-by: Lukas Czerner <[email protected]> Cc: Tadeusz Struk <[email protected]> Tested-by: Tadeusz Struk <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Theodore Ts'o <[email protected]>
1 parent 307af6c commit 1e1c2b8

File tree

1 file changed

+20
-1
lines changed

1 file changed

+20
-1
lines changed

fs/ext4/mballoc.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5934,6 +5934,15 @@ static void ext4_mb_clear_bb(handle_t *handle, struct inode *inode,
59345934

59355935
sbi = EXT4_SB(sb);
59365936

5937+
if (!(flags & EXT4_FREE_BLOCKS_VALIDATED) &&
5938+
!ext4_inode_block_valid(inode, block, count)) {
5939+
ext4_error(sb, "Freeing blocks in system zone - "
5940+
"Block = %llu, count = %lu", block, count);
5941+
/* err = 0. ext4_std_error should be a no op */
5942+
goto error_return;
5943+
}
5944+
flags |= EXT4_FREE_BLOCKS_VALIDATED;
5945+
59375946
do_more:
59385947
overflow = 0;
59395948
ext4_get_group_no_and_offset(sb, block, &block_group, &bit);
@@ -5950,6 +5959,8 @@ static void ext4_mb_clear_bb(handle_t *handle, struct inode *inode,
59505959
overflow = EXT4_C2B(sbi, bit) + count -
59515960
EXT4_BLOCKS_PER_GROUP(sb);
59525961
count -= overflow;
5962+
/* The range changed so it's no longer validated */
5963+
flags &= ~EXT4_FREE_BLOCKS_VALIDATED;
59535964
}
59545965
count_clusters = EXT4_NUM_B2C(sbi, count);
59555966
bitmap_bh = ext4_read_block_bitmap(sb, block_group);
@@ -5964,7 +5975,8 @@ static void ext4_mb_clear_bb(handle_t *handle, struct inode *inode,
59645975
goto error_return;
59655976
}
59665977

5967-
if (!ext4_inode_block_valid(inode, block, count)) {
5978+
if (!(flags & EXT4_FREE_BLOCKS_VALIDATED) &&
5979+
!ext4_inode_block_valid(inode, block, count)) {
59685980
ext4_error(sb, "Freeing blocks in system zone - "
59695981
"Block = %llu, count = %lu", block, count);
59705982
/* err = 0. ext4_std_error should be a no op */
@@ -6087,6 +6099,8 @@ static void ext4_mb_clear_bb(handle_t *handle, struct inode *inode,
60876099
block += count;
60886100
count = overflow;
60896101
put_bh(bitmap_bh);
6102+
/* The range changed so it's no longer validated */
6103+
flags &= ~EXT4_FREE_BLOCKS_VALIDATED;
60906104
goto do_more;
60916105
}
60926106
error_return:
@@ -6133,6 +6147,7 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,
61336147
"block = %llu, count = %lu", block, count);
61346148
return;
61356149
}
6150+
flags |= EXT4_FREE_BLOCKS_VALIDATED;
61366151

61376152
ext4_debug("freeing block %llu\n", block);
61386153
trace_ext4_free_blocks(inode, block, count, flags);
@@ -6164,6 +6179,8 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,
61646179
block -= overflow;
61656180
count += overflow;
61666181
}
6182+
/* The range changed so it's no longer validated */
6183+
flags &= ~EXT4_FREE_BLOCKS_VALIDATED;
61676184
}
61686185
overflow = EXT4_LBLK_COFF(sbi, count);
61696186
if (overflow) {
@@ -6174,6 +6191,8 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,
61746191
return;
61756192
} else
61766193
count += sbi->s_cluster_ratio - overflow;
6194+
/* The range changed so it's no longer validated */
6195+
flags &= ~EXT4_FREE_BLOCKS_VALIDATED;
61776196
}
61786197

61796198
if (!bh && (flags & EXT4_FREE_BLOCKS_FORGET)) {

0 commit comments

Comments
 (0)