ext4: fix potential race between s_group_info online resizing and access
[ Upstream commit df3da4ea5a0fc5d115c90d5aa6caa4dd433750a7 ] During an online resize an array of pointers to s_group_info gets replaced so it can get enlarged. If there is a concurrent access to the array in ext4_get_group_info() and this memory has been reused then this can lead to an invalid memory access. Link: https://bugzilla.kernel.org/show_bug.cgi?id=206443 Link: https://lore.kernel.org/r/20200221053458.730016-3-tytso@mit.edu Signed-off-by: Suraj Jitindar Singh <surajjs@amazon.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu> Reviewed-by: Balbir Singh <sblbir@amazon.com> Cc: stable@kernel.org Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
0d1f41c5eb
commit
8ef97a05d6
|
@ -1380,7 +1380,7 @@ struct ext4_sb_info {
|
||||||
s64 s_r_inodes_count;
|
s64 s_r_inodes_count;
|
||||||
|
|
||||||
/* for buddy allocator */
|
/* for buddy allocator */
|
||||||
struct ext4_group_info ***s_group_info;
|
struct ext4_group_info ** __rcu *s_group_info;
|
||||||
struct inode *s_buddy_cache;
|
struct inode *s_buddy_cache;
|
||||||
spinlock_t s_md_lock;
|
spinlock_t s_md_lock;
|
||||||
unsigned short *s_mb_offsets;
|
unsigned short *s_mb_offsets;
|
||||||
|
@ -2868,13 +2868,13 @@ static inline
|
||||||
struct ext4_group_info *ext4_get_group_info(struct super_block *sb,
|
struct ext4_group_info *ext4_get_group_info(struct super_block *sb,
|
||||||
ext4_group_t group)
|
ext4_group_t group)
|
||||||
{
|
{
|
||||||
struct ext4_group_info ***grp_info;
|
struct ext4_group_info **grp_info;
|
||||||
long indexv, indexh;
|
long indexv, indexh;
|
||||||
BUG_ON(group >= EXT4_SB(sb)->s_groups_count);
|
BUG_ON(group >= EXT4_SB(sb)->s_groups_count);
|
||||||
grp_info = EXT4_SB(sb)->s_group_info;
|
|
||||||
indexv = group >> (EXT4_DESC_PER_BLOCK_BITS(sb));
|
indexv = group >> (EXT4_DESC_PER_BLOCK_BITS(sb));
|
||||||
indexh = group & ((EXT4_DESC_PER_BLOCK(sb)) - 1);
|
indexh = group & ((EXT4_DESC_PER_BLOCK(sb)) - 1);
|
||||||
return grp_info[indexv][indexh];
|
grp_info = sbi_array_rcu_deref(EXT4_SB(sb), s_group_info, indexv);
|
||||||
|
return grp_info[indexh];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -2444,7 +2444,7 @@ int ext4_mb_alloc_groupinfo(struct super_block *sb, ext4_group_t ngroups)
|
||||||
{
|
{
|
||||||
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
||||||
unsigned size;
|
unsigned size;
|
||||||
struct ext4_group_info ***new_groupinfo;
|
struct ext4_group_info ***old_groupinfo, ***new_groupinfo;
|
||||||
|
|
||||||
size = (ngroups + EXT4_DESC_PER_BLOCK(sb) - 1) >>
|
size = (ngroups + EXT4_DESC_PER_BLOCK(sb) - 1) >>
|
||||||
EXT4_DESC_PER_BLOCK_BITS(sb);
|
EXT4_DESC_PER_BLOCK_BITS(sb);
|
||||||
|
@ -2457,13 +2457,16 @@ int ext4_mb_alloc_groupinfo(struct super_block *sb, ext4_group_t ngroups)
|
||||||
ext4_msg(sb, KERN_ERR, "can't allocate buddy meta group");
|
ext4_msg(sb, KERN_ERR, "can't allocate buddy meta group");
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
if (sbi->s_group_info) {
|
rcu_read_lock();
|
||||||
memcpy(new_groupinfo, sbi->s_group_info,
|
old_groupinfo = rcu_dereference(sbi->s_group_info);
|
||||||
|
if (old_groupinfo)
|
||||||
|
memcpy(new_groupinfo, old_groupinfo,
|
||||||
sbi->s_group_info_size * sizeof(*sbi->s_group_info));
|
sbi->s_group_info_size * sizeof(*sbi->s_group_info));
|
||||||
kvfree(sbi->s_group_info);
|
rcu_read_unlock();
|
||||||
}
|
rcu_assign_pointer(sbi->s_group_info, new_groupinfo);
|
||||||
sbi->s_group_info = new_groupinfo;
|
|
||||||
sbi->s_group_info_size = size / sizeof(*sbi->s_group_info);
|
sbi->s_group_info_size = size / sizeof(*sbi->s_group_info);
|
||||||
|
if (old_groupinfo)
|
||||||
|
ext4_kvfree_array_rcu(old_groupinfo);
|
||||||
ext4_debug("allocated s_groupinfo array for %d meta_bg's\n",
|
ext4_debug("allocated s_groupinfo array for %d meta_bg's\n",
|
||||||
sbi->s_group_info_size);
|
sbi->s_group_info_size);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2475,6 +2478,7 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group,
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
int metalen = 0;
|
int metalen = 0;
|
||||||
|
int idx = group >> EXT4_DESC_PER_BLOCK_BITS(sb);
|
||||||
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
||||||
struct ext4_group_info **meta_group_info;
|
struct ext4_group_info **meta_group_info;
|
||||||
struct kmem_cache *cachep = get_groupinfo_cache(sb->s_blocksize_bits);
|
struct kmem_cache *cachep = get_groupinfo_cache(sb->s_blocksize_bits);
|
||||||
|
@ -2493,12 +2497,12 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group,
|
||||||
"for a buddy group");
|
"for a buddy group");
|
||||||
goto exit_meta_group_info;
|
goto exit_meta_group_info;
|
||||||
}
|
}
|
||||||
sbi->s_group_info[group >> EXT4_DESC_PER_BLOCK_BITS(sb)] =
|
rcu_read_lock();
|
||||||
meta_group_info;
|
rcu_dereference(sbi->s_group_info)[idx] = meta_group_info;
|
||||||
|
rcu_read_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
meta_group_info =
|
meta_group_info = sbi_array_rcu_deref(sbi, s_group_info, idx);
|
||||||
sbi->s_group_info[group >> EXT4_DESC_PER_BLOCK_BITS(sb)];
|
|
||||||
i = group & (EXT4_DESC_PER_BLOCK(sb) - 1);
|
i = group & (EXT4_DESC_PER_BLOCK(sb) - 1);
|
||||||
|
|
||||||
meta_group_info[i] = kmem_cache_zalloc(cachep, GFP_NOFS);
|
meta_group_info[i] = kmem_cache_zalloc(cachep, GFP_NOFS);
|
||||||
|
@ -2546,8 +2550,13 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group,
|
||||||
exit_group_info:
|
exit_group_info:
|
||||||
/* If a meta_group_info table has been allocated, release it now */
|
/* If a meta_group_info table has been allocated, release it now */
|
||||||
if (group % EXT4_DESC_PER_BLOCK(sb) == 0) {
|
if (group % EXT4_DESC_PER_BLOCK(sb) == 0) {
|
||||||
kfree(sbi->s_group_info[group >> EXT4_DESC_PER_BLOCK_BITS(sb)]);
|
struct ext4_group_info ***group_info;
|
||||||
sbi->s_group_info[group >> EXT4_DESC_PER_BLOCK_BITS(sb)] = NULL;
|
|
||||||
|
rcu_read_lock();
|
||||||
|
group_info = rcu_dereference(sbi->s_group_info);
|
||||||
|
kfree(group_info[idx]);
|
||||||
|
group_info[idx] = NULL;
|
||||||
|
rcu_read_unlock();
|
||||||
}
|
}
|
||||||
exit_meta_group_info:
|
exit_meta_group_info:
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -2560,6 +2569,7 @@ static int ext4_mb_init_backend(struct super_block *sb)
|
||||||
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
||||||
int err;
|
int err;
|
||||||
struct ext4_group_desc *desc;
|
struct ext4_group_desc *desc;
|
||||||
|
struct ext4_group_info ***group_info;
|
||||||
struct kmem_cache *cachep;
|
struct kmem_cache *cachep;
|
||||||
|
|
||||||
err = ext4_mb_alloc_groupinfo(sb, ngroups);
|
err = ext4_mb_alloc_groupinfo(sb, ngroups);
|
||||||
|
@ -2594,11 +2604,16 @@ err_freebuddy:
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
kmem_cache_free(cachep, ext4_get_group_info(sb, i));
|
kmem_cache_free(cachep, ext4_get_group_info(sb, i));
|
||||||
i = sbi->s_group_info_size;
|
i = sbi->s_group_info_size;
|
||||||
|
rcu_read_lock();
|
||||||
|
group_info = rcu_dereference(sbi->s_group_info);
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
kfree(sbi->s_group_info[i]);
|
kfree(group_info[i]);
|
||||||
|
rcu_read_unlock();
|
||||||
iput(sbi->s_buddy_cache);
|
iput(sbi->s_buddy_cache);
|
||||||
err_freesgi:
|
err_freesgi:
|
||||||
kvfree(sbi->s_group_info);
|
rcu_read_lock();
|
||||||
|
kvfree(rcu_dereference(sbi->s_group_info));
|
||||||
|
rcu_read_unlock();
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2787,7 +2802,7 @@ int ext4_mb_release(struct super_block *sb)
|
||||||
ext4_group_t ngroups = ext4_get_groups_count(sb);
|
ext4_group_t ngroups = ext4_get_groups_count(sb);
|
||||||
ext4_group_t i;
|
ext4_group_t i;
|
||||||
int num_meta_group_infos;
|
int num_meta_group_infos;
|
||||||
struct ext4_group_info *grinfo;
|
struct ext4_group_info *grinfo, ***group_info;
|
||||||
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
struct ext4_sb_info *sbi = EXT4_SB(sb);
|
||||||
struct kmem_cache *cachep = get_groupinfo_cache(sb->s_blocksize_bits);
|
struct kmem_cache *cachep = get_groupinfo_cache(sb->s_blocksize_bits);
|
||||||
|
|
||||||
|
@ -2805,9 +2820,12 @@ int ext4_mb_release(struct super_block *sb)
|
||||||
num_meta_group_infos = (ngroups +
|
num_meta_group_infos = (ngroups +
|
||||||
EXT4_DESC_PER_BLOCK(sb) - 1) >>
|
EXT4_DESC_PER_BLOCK(sb) - 1) >>
|
||||||
EXT4_DESC_PER_BLOCK_BITS(sb);
|
EXT4_DESC_PER_BLOCK_BITS(sb);
|
||||||
|
rcu_read_lock();
|
||||||
|
group_info = rcu_dereference(sbi->s_group_info);
|
||||||
for (i = 0; i < num_meta_group_infos; i++)
|
for (i = 0; i < num_meta_group_infos; i++)
|
||||||
kfree(sbi->s_group_info[i]);
|
kfree(group_info[i]);
|
||||||
kvfree(sbi->s_group_info);
|
kvfree(group_info);
|
||||||
|
rcu_read_unlock();
|
||||||
}
|
}
|
||||||
kfree(sbi->s_mb_offsets);
|
kfree(sbi->s_mb_offsets);
|
||||||
kfree(sbi->s_mb_maxs);
|
kfree(sbi->s_mb_maxs);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user