FAILED: patch "[PATCH] ext4: fix potential race between online resizing and write" failed to apply to 4.4-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 4.4-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);