ext4: fix potential race between online resizing and write operations
During an online resize an array of pointers to buffer heads gets replaced so it can get enlarged. If there is a racing block allocation or deallocation which uses the old array, and the old array has gotten reused this can lead to a GPF or some other random kernel memory getting modified. Link: https://bugzilla.kernel.org/show_bug.cgi?id=206443 Link: https://lore.kernel.org/r/20200221053458.730016-2-tytso@mit.edu Reported-by: Suraj Jitindar Singh <surajjs@amazon.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu> Cc: stable@kernel.org
This commit is contained in:
@@ -1014,6 +1014,7 @@ static void ext4_put_super(struct super_block *sb)
|
||||
{
|
||||
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
||||
struct ext4_super_block *es = sbi->s_es;
|
||||
struct buffer_head **group_desc;
|
||||
int aborted = 0;
|
||||
int i, err;
|
||||
|
||||
@@ -1046,9 +1047,12 @@ static void ext4_put_super(struct super_block *sb)
|
||||
if (!sb_rdonly(sb))
|
||||
ext4_commit_super(sb, 1);
|
||||
|
||||
rcu_read_lock();
|
||||
group_desc = rcu_dereference(sbi->s_group_desc);
|
||||
for (i = 0; i < sbi->s_gdb_count; i++)
|
||||
brelse(sbi->s_group_desc[i]);
|
||||
kvfree(sbi->s_group_desc);
|
||||
brelse(group_desc[i]);
|
||||
kvfree(group_desc);
|
||||
rcu_read_unlock();
|
||||
kvfree(sbi->s_flex_groups);
|
||||
percpu_counter_destroy(&sbi->s_freeclusters_counter);
|
||||
percpu_counter_destroy(&sbi->s_freeinodes_counter);
|
||||
@@ -3634,7 +3638,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct dax_device *dax_dev = fs_dax_get_by_bdev(sb->s_bdev);
|
||||
char *orig_data = kstrdup(data, GFP_KERNEL);
|
||||
struct buffer_head *bh;
|
||||
struct buffer_head *bh, **group_desc;
|
||||
struct ext4_super_block *es = NULL;
|
||||
struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
|
||||
ext4_fsblk_t block;
|
||||
@@ -4290,9 +4294,10 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
||||
goto failed_mount;
|
||||
}
|
||||
}
|
||||
sbi->s_group_desc = kvmalloc_array(db_count,
|
||||
sizeof(struct buffer_head *),
|
||||
GFP_KERNEL);
|
||||
rcu_assign_pointer(sbi->s_group_desc,
|
||||
kvmalloc_array(db_count,
|
||||
sizeof(struct buffer_head *),
|
||||
GFP_KERNEL));
|
||||
if (sbi->s_group_desc == NULL) {
|
||||
ext4_msg(sb, KERN_ERR, "not enough memory");
|
||||
ret = -ENOMEM;
|
||||
@@ -4308,14 +4313,19 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
||||
}
|
||||
|
||||
for (i = 0; i < db_count; i++) {
|
||||
struct buffer_head *bh;
|
||||
|
||||
block = descriptor_loc(sb, logical_sb_block, i);
|
||||
sbi->s_group_desc[i] = sb_bread_unmovable(sb, block);
|
||||
if (!sbi->s_group_desc[i]) {
|
||||
bh = sb_bread_unmovable(sb, block);
|
||||
if (!bh) {
|
||||
ext4_msg(sb, KERN_ERR,
|
||||
"can't read group descriptor %d", i);
|
||||
db_count = i;
|
||||
goto failed_mount2;
|
||||
}
|
||||
rcu_read_lock();
|
||||
rcu_dereference(sbi->s_group_desc)[i] = bh;
|
||||
rcu_read_unlock();
|
||||
}
|
||||
sbi->s_gdb_count = db_count;
|
||||
if (!ext4_check_descriptors(sb, logical_sb_block, &first_not_zeroed)) {
|
||||
@@ -4717,9 +4727,12 @@ failed_mount3:
|
||||
if (sbi->s_mmp_tsk)
|
||||
kthread_stop(sbi->s_mmp_tsk);
|
||||
failed_mount2:
|
||||
rcu_read_lock();
|
||||
group_desc = rcu_dereference(sbi->s_group_desc);
|
||||
for (i = 0; i < db_count; i++)
|
||||
brelse(sbi->s_group_desc[i]);
|
||||
kvfree(sbi->s_group_desc);
|
||||
brelse(group_desc[i]);
|
||||
kvfree(group_desc);
|
||||
rcu_read_unlock();
|
||||
failed_mount:
|
||||
if (sbi->s_chksum_driver)
|
||||
crypto_free_shash(sbi->s_chksum_driver);
|
||||
|
Reference in New Issue
Block a user