Skip to content

Commit 7c99072

Browse files
Suraj Jitindar Singhtytso
authored andcommitted
ext4: fix potential race between s_flex_groups online resizing and access
During an online resize an array of s_flex_groups structures gets replaced so it can get enlarged. If there is a concurrent access to the array and this memory has been reused then this can lead to an invalid memory access. The s_flex_group array has been converted into an array of pointers rather than an array of structures. This is to ensure that the information contained in the structures cannot get out of sync during a resize due to an accessor updating the value in the old structure after it has been copied but before the array pointer is updated. Since the structures them- selves are no longer copied but only the pointers to them this case is mitigated. Link: https://bugzilla.kernel.org/show_bug.cgi?id=206443 Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Suraj Jitindar Singh <[email protected]> Signed-off-by: Theodore Ts'o <[email protected]> Cc: [email protected]
1 parent df3da4e commit 7c99072

File tree

5 files changed

+76
-37
lines changed

5 files changed

+76
-37
lines changed

fs/ext4/ext4.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,7 @@ struct ext4_sb_info {
15121512
unsigned int s_extent_max_zeroout_kb;
15131513

15141514
unsigned int s_log_groups_per_flex;
1515-
struct flex_groups *s_flex_groups;
1515+
struct flex_groups * __rcu *s_flex_groups;
15161516
ext4_group_t s_flex_groups_allocated;
15171517

15181518
/* workqueue for reserved extent conversions (buffered io) */

fs/ext4/ialloc.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -328,11 +328,13 @@ void ext4_free_inode(handle_t *handle, struct inode *inode)
328328

329329
percpu_counter_inc(&sbi->s_freeinodes_counter);
330330
if (sbi->s_log_groups_per_flex) {
331-
ext4_group_t f = ext4_flex_group(sbi, block_group);
331+
struct flex_groups *fg;
332332

333-
atomic_inc(&sbi->s_flex_groups[f].free_inodes);
333+
fg = sbi_array_rcu_deref(sbi, s_flex_groups,
334+
ext4_flex_group(sbi, block_group));
335+
atomic_inc(&fg->free_inodes);
334336
if (is_directory)
335-
atomic_dec(&sbi->s_flex_groups[f].used_dirs);
337+
atomic_dec(&fg->used_dirs);
336338
}
337339
BUFFER_TRACE(bh2, "call ext4_handle_dirty_metadata");
338340
fatal = ext4_handle_dirty_metadata(handle, NULL, bh2);
@@ -368,12 +370,13 @@ static void get_orlov_stats(struct super_block *sb, ext4_group_t g,
368370
int flex_size, struct orlov_stats *stats)
369371
{
370372
struct ext4_group_desc *desc;
371-
struct flex_groups *flex_group = EXT4_SB(sb)->s_flex_groups;
372373

373374
if (flex_size > 1) {
374-
stats->free_inodes = atomic_read(&flex_group[g].free_inodes);
375-
stats->free_clusters = atomic64_read(&flex_group[g].free_clusters);
376-
stats->used_dirs = atomic_read(&flex_group[g].used_dirs);
375+
struct flex_groups *fg = sbi_array_rcu_deref(EXT4_SB(sb),
376+
s_flex_groups, g);
377+
stats->free_inodes = atomic_read(&fg->free_inodes);
378+
stats->free_clusters = atomic64_read(&fg->free_clusters);
379+
stats->used_dirs = atomic_read(&fg->used_dirs);
377380
return;
378381
}
379382

@@ -1054,7 +1057,8 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
10541057
if (sbi->s_log_groups_per_flex) {
10551058
ext4_group_t f = ext4_flex_group(sbi, group);
10561059

1057-
atomic_inc(&sbi->s_flex_groups[f].used_dirs);
1060+
atomic_inc(&sbi_array_rcu_deref(sbi, s_flex_groups,
1061+
f)->used_dirs);
10581062
}
10591063
}
10601064
if (ext4_has_group_desc_csum(sb)) {
@@ -1077,7 +1081,8 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
10771081

10781082
if (sbi->s_log_groups_per_flex) {
10791083
flex_group = ext4_flex_group(sbi, group);
1080-
atomic_dec(&sbi->s_flex_groups[flex_group].free_inodes);
1084+
atomic_dec(&sbi_array_rcu_deref(sbi, s_flex_groups,
1085+
flex_group)->free_inodes);
10811086
}
10821087

10831088
inode->i_ino = ino + group * EXT4_INODES_PER_GROUP(sb);

fs/ext4/mballoc.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3038,7 +3038,8 @@ ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac,
30383038
ext4_group_t flex_group = ext4_flex_group(sbi,
30393039
ac->ac_b_ex.fe_group);
30403040
atomic64_sub(ac->ac_b_ex.fe_len,
3041-
&sbi->s_flex_groups[flex_group].free_clusters);
3041+
&sbi_array_rcu_deref(sbi, s_flex_groups,
3042+
flex_group)->free_clusters);
30423043
}
30433044

30443045
err = ext4_handle_dirty_metadata(handle, NULL, bitmap_bh);
@@ -4936,7 +4937,8 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,
49364937
if (sbi->s_log_groups_per_flex) {
49374938
ext4_group_t flex_group = ext4_flex_group(sbi, block_group);
49384939
atomic64_add(count_clusters,
4939-
&sbi->s_flex_groups[flex_group].free_clusters);
4940+
&sbi_array_rcu_deref(sbi, s_flex_groups,
4941+
flex_group)->free_clusters);
49404942
}
49414943

49424944
/*
@@ -5093,7 +5095,8 @@ int ext4_group_add_blocks(handle_t *handle, struct super_block *sb,
50935095
if (sbi->s_log_groups_per_flex) {
50945096
ext4_group_t flex_group = ext4_flex_group(sbi, block_group);
50955097
atomic64_add(clusters_freed,
5096-
&sbi->s_flex_groups[flex_group].free_clusters);
5098+
&sbi_array_rcu_deref(sbi, s_flex_groups,
5099+
flex_group)->free_clusters);
50975100
}
50985101

50995102
ext4_mb_unload_buddy(&e4b);

fs/ext4/resize.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,11 +1430,14 @@ static void ext4_update_super(struct super_block *sb,
14301430
percpu_counter_read(&sbi->s_freeclusters_counter));
14311431
if (ext4_has_feature_flex_bg(sb) && sbi->s_log_groups_per_flex) {
14321432
ext4_group_t flex_group;
1433+
struct flex_groups *fg;
1434+
14331435
flex_group = ext4_flex_group(sbi, group_data[0].group);
1436+
fg = sbi_array_rcu_deref(sbi, s_flex_groups, flex_group);
14341437
atomic64_add(EXT4_NUM_B2C(sbi, free_blocks),
1435-
&sbi->s_flex_groups[flex_group].free_clusters);
1438+
&fg->free_clusters);
14361439
atomic_add(EXT4_INODES_PER_GROUP(sb) * flex_gd->count,
1437-
&sbi->s_flex_groups[flex_group].free_inodes);
1440+
&fg->free_inodes);
14381441
}
14391442

14401443
/*

fs/ext4/super.c

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,7 @@ static void ext4_put_super(struct super_block *sb)
10151015
struct ext4_sb_info *sbi = EXT4_SB(sb);
10161016
struct ext4_super_block *es = sbi->s_es;
10171017
struct buffer_head **group_desc;
1018+
struct flex_groups **flex_groups;
10181019
int aborted = 0;
10191020
int i, err;
10201021

@@ -1052,8 +1053,13 @@ static void ext4_put_super(struct super_block *sb)
10521053
for (i = 0; i < sbi->s_gdb_count; i++)
10531054
brelse(group_desc[i]);
10541055
kvfree(group_desc);
1056+
flex_groups = rcu_dereference(sbi->s_flex_groups);
1057+
if (flex_groups) {
1058+
for (i = 0; i < sbi->s_flex_groups_allocated; i++)
1059+
kvfree(flex_groups[i]);
1060+
kvfree(flex_groups);
1061+
}
10551062
rcu_read_unlock();
1056-
kvfree(sbi->s_flex_groups);
10571063
percpu_counter_destroy(&sbi->s_freeclusters_counter);
10581064
percpu_counter_destroy(&sbi->s_freeinodes_counter);
10591065
percpu_counter_destroy(&sbi->s_dirs_counter);
@@ -2384,8 +2390,8 @@ static int ext4_setup_super(struct super_block *sb, struct ext4_super_block *es,
23842390
int ext4_alloc_flex_bg_array(struct super_block *sb, ext4_group_t ngroup)
23852391
{
23862392
struct ext4_sb_info *sbi = EXT4_SB(sb);
2387-
struct flex_groups *new_groups;
2388-
int size;
2393+
struct flex_groups **old_groups, **new_groups;
2394+
int size, i;
23892395

23902396
if (!sbi->s_log_groups_per_flex)
23912397
return 0;
@@ -2394,29 +2400,45 @@ int ext4_alloc_flex_bg_array(struct super_block *sb, ext4_group_t ngroup)
23942400
if (size <= sbi->s_flex_groups_allocated)
23952401
return 0;
23962402

2397-
size = roundup_pow_of_two(size * sizeof(struct flex_groups));
2398-
new_groups = kvzalloc(size, GFP_KERNEL);
2403+
new_groups = kvzalloc(roundup_pow_of_two(size *
2404+
sizeof(*sbi->s_flex_groups)), GFP_KERNEL);
23992405
if (!new_groups) {
2400-
ext4_msg(sb, KERN_ERR, "not enough memory for %d flex groups",
2401-
size / (int) sizeof(struct flex_groups));
2406+
ext4_msg(sb, KERN_ERR,
2407+
"not enough memory for %d flex group pointers", size);
24022408
return -ENOMEM;
24032409
}
2404-
2405-
if (sbi->s_flex_groups) {
2406-
memcpy(new_groups, sbi->s_flex_groups,
2407-
(sbi->s_flex_groups_allocated *
2408-
sizeof(struct flex_groups)));
2409-
kvfree(sbi->s_flex_groups);
2410+
for (i = sbi->s_flex_groups_allocated; i < size; i++) {
2411+
new_groups[i] = kvzalloc(roundup_pow_of_two(
2412+
sizeof(struct flex_groups)),
2413+
GFP_KERNEL);
2414+
if (!new_groups[i]) {
2415+
for (i--; i >= sbi->s_flex_groups_allocated; i--)
2416+
kvfree(new_groups[i]);
2417+
kvfree(new_groups);
2418+
ext4_msg(sb, KERN_ERR,
2419+
"not enough memory for %d flex groups", size);
2420+
return -ENOMEM;
2421+
}
24102422
}
2411-
sbi->s_flex_groups = new_groups;
2412-
sbi->s_flex_groups_allocated = size / sizeof(struct flex_groups);
2423+
rcu_read_lock();
2424+
old_groups = rcu_dereference(sbi->s_flex_groups);
2425+
if (old_groups)
2426+
memcpy(new_groups, old_groups,
2427+
(sbi->s_flex_groups_allocated *
2428+
sizeof(struct flex_groups *)));
2429+
rcu_read_unlock();
2430+
rcu_assign_pointer(sbi->s_flex_groups, new_groups);
2431+
sbi->s_flex_groups_allocated = size;
2432+
if (old_groups)
2433+
ext4_kvfree_array_rcu(old_groups);
24132434
return 0;
24142435
}
24152436

24162437
static int ext4_fill_flex_info(struct super_block *sb)
24172438
{
24182439
struct ext4_sb_info *sbi = EXT4_SB(sb);
24192440
struct ext4_group_desc *gdp = NULL;
2441+
struct flex_groups *fg;
24202442
ext4_group_t flex_group;
24212443
int i, err;
24222444

@@ -2434,12 +2456,11 @@ static int ext4_fill_flex_info(struct super_block *sb)
24342456
gdp = ext4_get_group_desc(sb, i, NULL);
24352457

24362458
flex_group = ext4_flex_group(sbi, i);
2437-
atomic_add(ext4_free_inodes_count(sb, gdp),
2438-
&sbi->s_flex_groups[flex_group].free_inodes);
2459+
fg = sbi_array_rcu_deref(sbi, s_flex_groups, flex_group);
2460+
atomic_add(ext4_free_inodes_count(sb, gdp), &fg->free_inodes);
24392461
atomic64_add(ext4_free_group_clusters(sb, gdp),
2440-
&sbi->s_flex_groups[flex_group].free_clusters);
2441-
atomic_add(ext4_used_dirs_count(sb, gdp),
2442-
&sbi->s_flex_groups[flex_group].used_dirs);
2462+
&fg->free_clusters);
2463+
atomic_add(ext4_used_dirs_count(sb, gdp), &fg->used_dirs);
24432464
}
24442465

24452466
return 1;
@@ -3641,6 +3662,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
36413662
struct buffer_head *bh, **group_desc;
36423663
struct ext4_super_block *es = NULL;
36433664
struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
3665+
struct flex_groups **flex_groups;
36443666
ext4_fsblk_t block;
36453667
ext4_fsblk_t sb_block = get_sb_block(&data);
36463668
ext4_fsblk_t logical_sb_block;
@@ -4692,8 +4714,14 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
46924714
ext4_unregister_li_request(sb);
46934715
failed_mount6:
46944716
ext4_mb_release(sb);
4695-
if (sbi->s_flex_groups)
4696-
kvfree(sbi->s_flex_groups);
4717+
rcu_read_lock();
4718+
flex_groups = rcu_dereference(sbi->s_flex_groups);
4719+
if (flex_groups) {
4720+
for (i = 0; i < sbi->s_flex_groups_allocated; i++)
4721+
kvfree(flex_groups[i]);
4722+
kvfree(flex_groups);
4723+
}
4724+
rcu_read_unlock();
46974725
percpu_counter_destroy(&sbi->s_freeclusters_counter);
46984726
percpu_counter_destroy(&sbi->s_freeinodes_counter);
46994727
percpu_counter_destroy(&sbi->s_dirs_counter);

0 commit comments

Comments
 (0)