Commit df3da4ea authored by Suraj Jitindar Singh's avatar Suraj Jitindar Singh Committed by Theodore Ts'o

ext4: fix potential race between s_group_info online resizing and access

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.eduSigned-off-by: default avatarSuraj Jitindar Singh <surajjs@amazon.com>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Reviewed-by: default avatarBalbir Singh <sblbir@amazon.com>
Cc: stable@kernel.org
parent 1d0c3924
...@@ -1462,7 +1462,7 @@ struct ext4_sb_info { ...@@ -1462,7 +1462,7 @@ struct ext4_sb_info {
#endif #endif
/* 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;
...@@ -2994,13 +2994,13 @@ static inline ...@@ -2994,13 +2994,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];
} }
/* /*
......
...@@ -2356,7 +2356,7 @@ int ext4_mb_alloc_groupinfo(struct super_block *sb, ext4_group_t ngroups) ...@@ -2356,7 +2356,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);
...@@ -2369,13 +2369,16 @@ int ext4_mb_alloc_groupinfo(struct super_block *sb, ext4_group_t ngroups) ...@@ -2369,13 +2369,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;
...@@ -2387,6 +2390,7 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group, ...@@ -2387,6 +2390,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);
...@@ -2405,12 +2409,12 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group, ...@@ -2405,12 +2409,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);
...@@ -2458,8 +2462,13 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group, ...@@ -2458,8 +2462,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;
...@@ -2472,6 +2481,7 @@ static int ext4_mb_init_backend(struct super_block *sb) ...@@ -2472,6 +2481,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);
...@@ -2507,11 +2517,16 @@ static int ext4_mb_init_backend(struct super_block *sb) ...@@ -2507,11 +2517,16 @@ static int ext4_mb_init_backend(struct super_block *sb)
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;
} }
...@@ -2700,7 +2715,7 @@ int ext4_mb_release(struct super_block *sb) ...@@ -2700,7 +2715,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);
...@@ -2719,9 +2734,12 @@ int ext4_mb_release(struct super_block *sb) ...@@ -2719,9 +2734,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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment