FAILED: patch "[PATCH] ext4: fix potential race between online resizing and write" failed to apply to 4.9-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 4.9-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 1d0c3924a92e69bfa91163bda83c12a994b4d106 Mon Sep 17 00:00:00 2001
From: Theodore Ts'o <[email protected]>
Date: Sat, 15 Feb 2020 16:40:37 -0500
Subject: [PATCH] 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.
fs/ext4/balloc.c +11 -3
@@ -270,6 +270,7 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,ext4_group_t ngroups =ext4_get_groups_count (sb);struct ext4_group_desc *desc;struct ext4_sb_info *sbi =EXT4_SB (sb); +struct buffer_head *bh_p;if (block_group >= ngroups) { ext4_error(sb, "block_group >= groups_count - block_group = %u,"
@@ -280,7 +281,14 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb, group_desc = block_group >>EXT4_DESC_PER_BLOCK_BITS (sb); offset = block_group & (EXT4_DESC_PER_BLOCK (sb) -1 ); -if (!sbi->s_group_desc[group_desc]) { + bh_p =sbi_array_rcu_deref (sbi, s_group_desc, group_desc); +/* +* sbi_array_rcu_deref returns with rcu unlocked, this is ok since +* the pointer being dereferenced won't be dereferenced again. By +* looking at the usage in add_new_gdb() the value isn't modified, +* just the pointer, and so it remains valid. +*/ +if (!bh_p) {ext4_error (sb,"Group descriptor not loaded - " "block_group = %u, group_desc = %u, desc = %u" , block_group, group_desc, offset);
@@ -288,10 +296,10 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb, } desc = (struct ext4_group_desc *)( - (__u8 *)sbi->s_group_desc[group_desc]->b_data + + (__u8 *)bh_p->b_data + offset *EXT4_DESC_SIZE (sb));if (bh) - *bh = sbi->s_group_desc[group_desc]; + *bh = bh_p;return desc; }
fs/ext4/ext4.h +19 -1
@@ -1400,7 +1400,7 @@ struct ext4_sb_info {loff_t s_bitmap_maxbytes;/* max bytes for bitmap files */ struct buffer_head * s_sbh;/* Buffer containing the super block */ struct ext4_super_block *s_es;/* Pointer to the super block in the buffer */ -struct buffer_head **s_group_desc; +struct buffer_head *__rcu *s_group_desc;unsigned int s_mount_opt;unsigned int s_mount_opt2;unsigned int s_mount_flags;
@@ -1576,6 +1576,23 @@ static inline int ext4_valid_inum(struct super_block *sb, unsigned long ino) ino <= le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count)); } +/* +* Returns: sbi->field[index] +* Used to access an array element from the following sbi fields which require +* rcu protection to avoid dereferencing an invalid pointer due to reassignment +* - s_group_desc +* - s_group_info +* - s_flex_group +*/ +#define sbi_array_rcu_deref (sbi, field, index) \ +({ \ + typeof(*((sbi)->field)) _v; \ + rcu_read_lock(); \ + _v = ((typeof(_v)*)rcu_dereference((sbi)->field))[index]; \ + rcu_read_unlock(); \ + _v; \ +}) +/* * Simulate_fail codes */
@@ -2730,6 +2747,7 @@ extern int ext4_generic_delete_entry(handle_t *handle,extern bool ext4_empty_dir (struct inode *inode);/* resize.c */ +extern void ext4_kvfree_array_rcu (void *to_free);extern int ext4_group_add (struct super_block *sb,struct ext4_new_group_data *input); extern int ext4_group_extend(struct super_block *sb,
fs/ext4/resize.c +44 -11
110 lines not on this page · raw message
fs/ext4/super.c +23 -10
@@ -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 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)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);
PATCH 1/3 ext4: fix potential race between online resizing and write operations
From: Theodore Ts'o <[email protected]>
commit 1d0c3924a92e69bfa91163bda83c12a994b4d106 upstream.
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.
---
Changes against upstream:fs/ext4/ext4.h:
- Missing context, massage to apply
- No functional change
fs/ext4/super.c:ext4_fill_super()
- ext4_kvmalloc converted to kvmalloc_array upstream
- preservce ext4_kvmalloc usage
- No functional change
--- fs/ext4/balloc.c | 14 +++++++++--- fs/ext4/ext4.h | 20 +++++++++++++++++- fs/ext4/resize.c | 55 ++++++++++++++++++++++++++++++++++++++---------- fs/ext4/super.c | 31 +++++++++++++++++++-------- 4 files changed, 96 insertions(+), 24 deletions(-)
fs/ext4/balloc.c +11 -3
@@ -279,6 +279,7 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,ext4_group_t ngroups =ext4_get_groups_count (sb);struct ext4_group_desc *desc;struct ext4_sb_info *sbi =EXT4_SB (sb); +struct buffer_head *bh_p;if (block_group >= ngroups) { ext4_error(sb, "block_group >= groups_count - block_group = %u,"
@@ -289,7 +290,14 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb, group_desc = block_group >>EXT4_DESC_PER_BLOCK_BITS (sb); offset = block_group & (EXT4_DESC_PER_BLOCK (sb) -1 ); -if (!sbi->s_group_desc[group_desc]) { + bh_p =sbi_array_rcu_deref (sbi, s_group_desc, group_desc); +/* +* sbi_array_rcu_deref returns with rcu unlocked, this is ok since +* the pointer being dereferenced won't be dereferenced again. By +* looking at the usage in add_new_gdb() the value isn't modified, +* just the pointer, and so it remains valid. +*/ +if (!bh_p) {ext4_error (sb,"Group descriptor not loaded - " "block_group = %u, group_desc = %u, desc = %u" , block_group, group_desc, offset);
@@ -297,10 +305,10 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb, } desc = (struct ext4_group_desc *)( - (__u8 *)sbi->s_group_desc[group_desc]->b_data + + (__u8 *)bh_p->b_data + offset *EXT4_DESC_SIZE (sb));if (bh) - *bh = sbi->s_group_desc[group_desc]; + *bh = bh_p;return desc; }
fs/ext4/ext4.h +19 -1
@@ -1367,7 +1367,7 @@ struct ext4_sb_info {loff_t s_bitmap_maxbytes;/* max bytes for bitmap files */ struct buffer_head * s_sbh;/* Buffer containing the super block */ struct ext4_super_block *s_es;/* Pointer to the super block in the buffer */ -struct buffer_head **s_group_desc; +struct buffer_head *__rcu *s_group_desc;unsigned int s_mount_opt;unsigned int s_mount_opt2;unsigned int s_mount_flags;
@@ -1546,6 +1546,23 @@ static inline int ext4_valid_inum(struct super_block *sb, unsigned long ino) ino <= le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count)); } +/* +* Returns: sbi->field[index] +* Used to access an array element from the following sbi fields which require +* rcu protection to avoid dereferencing an invalid pointer due to reassignment +* - s_group_desc +* - s_group_info +* - s_flex_group +*/ +#define sbi_array_rcu_deref (sbi, field, index) \ +({ \ + typeof(*((sbi)->field)) _v; \ + rcu_read_lock(); \ + _v = ((typeof(_v)*)rcu_dereference((sbi)->field))[index]; \ + rcu_read_unlock(); \ + _v; \ +}) +/* * Inode dynamic state flags */
@@ -2552,6 +2569,7 @@ extern int ext4_generic_delete_entry(handle_t *handle,extern bool ext4_empty_dir (struct inode *inode);/* resize.c */ +extern void ext4_kvfree_array_rcu (void *to_free);extern int ext4_group_add (struct super_block *sb,struct ext4_new_group_data *input); extern int ext4_group_extend(struct super_block *sb,
fs/ext4/resize.c +44 -11
110 lines not on this page · raw message
fs/ext4/super.c +22 -9
@@ -826,6 +826,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;
@@ -857,9 +858,12 @@ static void ext4_put_super(struct super_block *sb)if (!(sb->s_flags &MS_RDONLY ))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);
@@ -3409,7 +3413,7 @@ static void ext4_set_resv_clusters(struct super_block *sb)static int ext4_fill_super (struct super_block *sb,void *data,int silent) {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;
@@ -3961,9 +3965,10 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) goto failed_mount; } } - sbi->s_group_desc =ext4_kvmalloc (db_count * +rcu_assign_pointer (sbi->s_group_desc, +ext4_kvmalloc (db_count *sizeof (struct buffer_head *), -GFP_KERNEL ); +GFP_KERNEL ));if (sbi->s_group_desc ==NULL ) {ext4_msg (sb,KERN_ERR ,"not enough memory" ); ret = -ENOMEM ;
@@ -3973,14 +3978,19 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)bgl_lock_init (sbi->s_blockgroup_lock);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)) {
@@ -4355,9 +4365,12 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)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);
Signature · 1 line
2.17.1
PATCH 2/3 ext4: fix potential race between s_group_info online resizing and access
commit df3da4ea5a0fc5d115c90d5aa6caa4dd433750a7 upstream.
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.
---
Changes against upstream:
none
--- fs/ext4/ext4.h | 8 ++++---- fs/ext4/mballoc.c | 52 +++++++++++++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 21 deletions(-)
fs/ext4/ext4.h +4 -4
@@ -1427,7 +1427,7 @@ struct ext4_sb_info {#endif /* for buddy allocator */ -struct ext4_group_info ***s_group_info; +struct ext4_group_info **__rcu *s_group_info;struct inode *s_buddy_cache;spinlock_t s_md_lock;unsigned short *s_mb_offsets;
@@ -2810,13 +2810,13 @@ static inlinestruct ext4_group_info *ext4_get_group_info (struct super_block *sb,ext4_group_t group) { -struct ext4_group_info ***grp_info; +struct ext4_group_info **grp_info;long indexv, indexh;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)); 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]; }/*
fs/ext4/mballoc.c +35 -17
114 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH 3/3 ext4: fix potential race between s_flex_groups online resizing and access
In reply to Suraj Jitindar Singhcommit 7c990728b99ed6fbe9c75fc202fce1172d9916da upstream.
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.
---
Changes against upstream:
fs/ext4/mballoc.c:ext4_group_add_blocks()
- blocks_freed converted to clusters_freed upstream
- Preserve blocks_freed usage
Changes against upstream:
fs/ext4/super.c:ext4_alloc_flex_bg_array()
- ext4_kvzalloc converted to kvzalloc upstream
- Preserve ext4_kvzalloc usage
--- fs/ext4/ext4.h | 2 +- fs/ext4/ialloc.c | 23 +++++++++------ fs/ext4/mballoc.c | 9 ++++-- fs/ext4/resize.c | 7 +++-- fs/ext4/super.c | 72 ++++++++++++++++++++++++++++++++--------------- 5 files changed, 76 insertions(+), 37 deletions(-)
fs/ext4/ext4.h +1 -1
@@ -1475,7 +1475,7 @@ struct ext4_sb_info {unsigned int s_extent_max_zeroout_kb;unsigned int s_log_groups_per_flex; -struct flex_groups *s_flex_groups; +struct flex_groups *__rcu *s_flex_groups;ext4_group_t s_flex_groups_allocated;/* workqueue for reserved extent conversions (buffered io) */
fs/ext4/ialloc.c +14 -9
@@ -331,11 +331,13 @@ void ext4_free_inode(handle_t *handle, struct inode *inode)percpu_counter_inc (&sbi->s_freeinodes_counter);if (sbi->s_log_groups_per_flex) { -ext4_group_t f =ext4_flex_group (sbi, block_group); +struct flex_groups *fg; -atomic_inc (&sbi->s_flex_groups[f].free_inodes); + fg =sbi_array_rcu_deref (sbi, s_flex_groups, +ext4_flex_group (sbi, block_group)); +atomic_inc (&fg->free_inodes);if (is_directory) -atomic_dec (&sbi->s_flex_groups[f].used_dirs); +atomic_dec (&fg->used_dirs); }BUFFER_TRACE (bh2,"call ext4_handle_dirty_metadata" ); fatal =ext4_handle_dirty_metadata (handle,NULL , bh2);
@@ -376,12 +378,13 @@ static void get_orlov_stats(struct super_block *sb, ext4_group_t g, int flex_size, struct orlov_stats *stats) { struct ext4_group_desc *desc; -struct flex_groups *flex_group =EXT4_SB (sb)->s_flex_groups;if (flex_size >1 ) { - stats->free_inodes =atomic_read (&flex_group[g].free_inodes); - stats->free_clusters =atomic64_read (&flex_group[g].free_clusters); - stats->used_dirs =atomic_read (&flex_group[g].used_dirs); +struct flex_groups *fg =sbi_array_rcu_deref (EXT4_SB (sb), + s_flex_groups, g); + stats->free_inodes =atomic_read (&fg->free_inodes); + stats->free_clusters =atomic64_read (&fg->free_clusters); + stats->used_dirs =atomic_read (&fg->used_dirs);return ; }
@@ -988,7 +991,8 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,if (sbi->s_log_groups_per_flex) {ext4_group_t f =ext4_flex_group (sbi, group); -atomic_inc (&sbi->s_flex_groups[f].used_dirs); +atomic_inc (&sbi_array_rcu_deref (sbi, s_flex_groups, + f)->used_dirs); } }if (ext4_has_group_desc_csum (sb)) {
@@ -1011,7 +1015,8 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,if (sbi->s_log_groups_per_flex) { flex_group =ext4_flex_group (sbi, group); -atomic_dec (&sbi->s_flex_groups[flex_group].free_inodes); +atomic_dec (&sbi_array_rcu_deref (sbi, s_flex_groups, + flex_group)->free_inodes); } inode->i_ino = ino + group *EXT4_INODES_PER_GROUP (sb);
fs/ext4/mballoc.c +6 -3
@@ -3016,7 +3016,8 @@ ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac,ext4_group_t flex_group =ext4_flex_group (sbi, ac->ac_b_ex.fe_group);atomic64_sub (ac->ac_b_ex.fe_len, - &sbi->s_flex_groups[flex_group].free_clusters); + &sbi_array_rcu_deref (sbi, s_flex_groups, + flex_group)->free_clusters); } err =ext4_handle_dirty_metadata (handle,NULL , bitmap_bh);
@@ -4906,7 +4907,8 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,if (sbi->s_log_groups_per_flex) {ext4_group_t flex_group =ext4_flex_group (sbi, block_group);atomic64_add (count_clusters, - &sbi->s_flex_groups[flex_group].free_clusters); + &sbi_array_rcu_deref (sbi, s_flex_groups, + flex_group)->free_clusters); }if (!(flags &EXT4_FREE_BLOCKS_NO_QUOT_UPDATE ))
@@ -5051,7 +5053,8 @@ int ext4_group_add_blocks(handle_t *handle, struct super_block *sb,if (sbi->s_log_groups_per_flex) {ext4_group_t flex_group =ext4_flex_group (sbi, block_group);atomic64_add (EXT4_NUM_B2C (sbi, blocks_freed), - &sbi->s_flex_groups[flex_group].free_clusters); + &sbi_array_rcu_deref (sbi, s_flex_groups, + flex_group)->free_clusters); }ext4_mb_unload_buddy (&e4b);
fs/ext4/resize.c +5 -2
@@ -1422,11 +1422,14 @@ static void ext4_update_super(struct super_block *sb, percpu_counter_read(&sbi->s_freeclusters_counter));if (ext4_has_feature_flex_bg (sb) && sbi->s_log_groups_per_flex) {ext4_group_t flex_group; +struct flex_groups *fg; + flex_group =ext4_flex_group (sbi, group_data[0 ].group); + fg =sbi_array_rcu_deref (sbi, s_flex_groups, flex_group);atomic64_add (EXT4_NUM_B2C (sbi, free_blocks), - &sbi->s_flex_groups[flex_group].free_clusters); + &fg->free_clusters);atomic_add (EXT4_INODES_PER_GROUP (sb) * flex_gd->count, - &sbi->s_flex_groups[flex_group].free_inodes); + &fg->free_inodes); }/*
fs/ext4/super.c +50 -22
126 lines not on this page · raw message
Signature · 1 line
2.17.1