]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
4.14-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 21 May 2018 08:24:16 +0000 (10:24 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 21 May 2018 08:24:16 +0000 (10:24 +0200)
added patches:
btrfs-fix-delalloc-inodes-invalidation-during-transaction-abort.patch
btrfs-split-btrfs_del_delalloc_inode-into-2-functions.patch

queue-4.14/btrfs-fix-delalloc-inodes-invalidation-during-transaction-abort.patch [new file with mode: 0644]
queue-4.14/btrfs-split-btrfs_del_delalloc_inode-into-2-functions.patch [new file with mode: 0644]
queue-4.14/series

diff --git a/queue-4.14/btrfs-fix-delalloc-inodes-invalidation-during-transaction-abort.patch b/queue-4.14/btrfs-fix-delalloc-inodes-invalidation-during-transaction-abort.patch
new file mode 100644 (file)
index 0000000..ee15a67
--- /dev/null
@@ -0,0 +1,131 @@
+From fe816d0f1d4c31c4c31d42ca78a87660565fc800 Mon Sep 17 00:00:00 2001
+From: Nikolay Borisov <nborisov@suse.com>
+Date: Fri, 27 Apr 2018 12:21:53 +0300
+Subject: btrfs: Fix delalloc inodes invalidation during transaction abort
+
+From: Nikolay Borisov <nborisov@suse.com>
+
+commit fe816d0f1d4c31c4c31d42ca78a87660565fc800 upstream.
+
+When a transaction is aborted btrfs_cleanup_transaction is called to
+cleanup all the various in-flight bits and pieces which migth be
+active. One of those is delalloc inodes - inodes which have dirty
+pages which haven't been persisted yet. Currently the process of
+freeing such delalloc inodes in exceptional circumstances such as
+transaction abort boiled down to calling btrfs_invalidate_inodes whose
+sole job is to invalidate the dentries for all inodes related to a
+root. This is in fact wrong and insufficient since such delalloc inodes
+will likely have pending pages or ordered-extents and will be linked to
+the sb->s_inode_list. This means that unmounting a btrfs instance with
+an aborted transaction could potentially lead inodes/their pages
+visible to the system long after their superblock has been freed. This
+in turn leads to a "use-after-free" situation once page shrink is
+triggered. This situation could be simulated by running generic/019
+which would cause such inodes to be left hanging, followed by
+generic/176 which causes memory pressure and page eviction which lead
+to touching the freed super block instance. This situation is
+additionally detected by the unmount code of VFS with the following
+message:
+
+"VFS: Busy inodes after unmount of Self-destruct in 5 seconds.  Have a nice day..."
+
+Additionally btrfs hits WARN_ON(!RB_EMPTY_ROOT(&root->inode_tree));
+in free_fs_root for the same reason.
+
+This patch aims to rectify the sitaution by doing the following:
+
+1. Change btrfs_destroy_delalloc_inodes so that it calls
+invalidate_inode_pages2 for every inode on the delalloc list, this
+ensures that all the pages of the inode are released. This function
+boils down to calling btrfs_releasepage. During test I observed cases
+where inodes on the delalloc list were having an i_count of 0, so this
+necessitates using igrab to be sure we are working on a non-freed inode.
+
+2. Since calling btrfs_releasepage might queue delayed iputs move the
+call out to btrfs_cleanup_transaction in btrfs_error_commit_super before
+calling run_delayed_iputs for the last time. This is necessary to ensure
+that delayed iputs are run.
+
+Note: this patch is tagged for 4.14 stable but the fix applies to older
+versions too but needs to be backported manually due to conflicts.
+
+CC: stable@vger.kernel.org # 4.14.x: 2b8773313494: btrfs: Split btrfs_del_delalloc_inode into 2 functions
+CC: stable@vger.kernel.org # 4.14.x
+Signed-off-by: Nikolay Borisov <nborisov@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+[ add comment to igrab ]
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ fs/btrfs/disk-io.c |   26 +++++++++++++++-----------
+ 1 file changed, 15 insertions(+), 11 deletions(-)
+
+--- a/fs/btrfs/disk-io.c
++++ b/fs/btrfs/disk-io.c
+@@ -3905,6 +3905,7 @@ void close_ctree(struct btrfs_fs_info *f
+       set_bit(BTRFS_FS_CLOSING_DONE, &fs_info->flags);
+       btrfs_free_qgroup_config(fs_info);
++      ASSERT(list_empty(&fs_info->delalloc_roots));
+       if (percpu_counter_sum(&fs_info->delalloc_bytes)) {
+               btrfs_info(fs_info, "at unmount delalloc count %lld",
+@@ -4203,15 +4204,15 @@ static int btrfs_check_super_valid(struc
+ static void btrfs_error_commit_super(struct btrfs_fs_info *fs_info)
+ {
++      /* cleanup FS via transaction */
++      btrfs_cleanup_transaction(fs_info);
++
+       mutex_lock(&fs_info->cleaner_mutex);
+       btrfs_run_delayed_iputs(fs_info);
+       mutex_unlock(&fs_info->cleaner_mutex);
+       down_write(&fs_info->cleanup_work_sem);
+       up_write(&fs_info->cleanup_work_sem);
+-
+-      /* cleanup FS via transaction */
+-      btrfs_cleanup_transaction(fs_info);
+ }
+ static void btrfs_destroy_ordered_extents(struct btrfs_root *root)
+@@ -4334,19 +4335,23 @@ static void btrfs_destroy_delalloc_inode
+       list_splice_init(&root->delalloc_inodes, &splice);
+       while (!list_empty(&splice)) {
++              struct inode *inode = NULL;
+               btrfs_inode = list_first_entry(&splice, struct btrfs_inode,
+                                              delalloc_inodes);
+-
+-              list_del_init(&btrfs_inode->delalloc_inodes);
+-              clear_bit(BTRFS_INODE_IN_DELALLOC_LIST,
+-                        &btrfs_inode->runtime_flags);
++              __btrfs_del_delalloc_inode(root, btrfs_inode);
+               spin_unlock(&root->delalloc_lock);
+-              btrfs_invalidate_inodes(btrfs_inode->root);
+-
++              /*
++               * Make sure we get a live inode and that it'll not disappear
++               * meanwhile.
++               */
++              inode = igrab(&btrfs_inode->vfs_inode);
++              if (inode) {
++                      invalidate_inode_pages2(inode->i_mapping);
++                      iput(inode);
++              }
+               spin_lock(&root->delalloc_lock);
+       }
+-
+       spin_unlock(&root->delalloc_lock);
+ }
+@@ -4362,7 +4367,6 @@ static void btrfs_destroy_all_delalloc_i
+       while (!list_empty(&splice)) {
+               root = list_first_entry(&splice, struct btrfs_root,
+                                        delalloc_root);
+-              list_del_init(&root->delalloc_root);
+               root = btrfs_grab_fs_root(root);
+               BUG_ON(!root);
+               spin_unlock(&fs_info->delalloc_root_lock);
diff --git a/queue-4.14/btrfs-split-btrfs_del_delalloc_inode-into-2-functions.patch b/queue-4.14/btrfs-split-btrfs_del_delalloc_inode-into-2-functions.patch
new file mode 100644 (file)
index 0000000..40e3cad
--- /dev/null
@@ -0,0 +1,66 @@
+From 2b8773313494ede83a26fb372466e634564002ed Mon Sep 17 00:00:00 2001
+From: Nikolay Borisov <nborisov@suse.com>
+Date: Fri, 27 Apr 2018 12:21:51 +0300
+Subject: btrfs: Split btrfs_del_delalloc_inode into 2 functions
+
+From: Nikolay Borisov <nborisov@suse.com>
+
+commit 2b8773313494ede83a26fb372466e634564002ed upstream.
+
+This is in preparation of fixing delalloc inodes leakage on transaction
+abort. Also export the new function.
+
+Signed-off-by: Nikolay Borisov <nborisov@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+Reviewed-by: Anand Jain <anand.jain@oracle.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ fs/btrfs/ctree.h |    2 ++
+ fs/btrfs/inode.c |   13 ++++++++++---
+ 2 files changed, 12 insertions(+), 3 deletions(-)
+
+--- a/fs/btrfs/ctree.h
++++ b/fs/btrfs/ctree.h
+@@ -3150,6 +3150,8 @@ noinline int can_nocow_extent(struct ino
+                             u64 *orig_start, u64 *orig_block_len,
+                             u64 *ram_bytes);
++void __btrfs_del_delalloc_inode(struct btrfs_root *root,
++                              struct btrfs_inode *inode);
+ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry);
+ int btrfs_set_inode_index(struct btrfs_inode *dir, u64 *index);
+ int btrfs_unlink_inode(struct btrfs_trans_handle *trans,
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -1754,12 +1754,12 @@ static void btrfs_add_delalloc_inodes(st
+       spin_unlock(&root->delalloc_lock);
+ }
+-static void btrfs_del_delalloc_inode(struct btrfs_root *root,
+-                                   struct btrfs_inode *inode)
++
++void __btrfs_del_delalloc_inode(struct btrfs_root *root,
++                              struct btrfs_inode *inode)
+ {
+       struct btrfs_fs_info *fs_info = btrfs_sb(inode->vfs_inode.i_sb);
+-      spin_lock(&root->delalloc_lock);
+       if (!list_empty(&inode->delalloc_inodes)) {
+               list_del_init(&inode->delalloc_inodes);
+               clear_bit(BTRFS_INODE_IN_DELALLOC_LIST,
+@@ -1772,6 +1772,13 @@ static void btrfs_del_delalloc_inode(str
+                       spin_unlock(&fs_info->delalloc_root_lock);
+               }
+       }
++}
++
++static void btrfs_del_delalloc_inode(struct btrfs_root *root,
++                                   struct btrfs_inode *inode)
++{
++      spin_lock(&root->delalloc_lock);
++      __btrfs_del_delalloc_inode(root, inode);
+       spin_unlock(&root->delalloc_lock);
+ }
index d9d590b6be5fc1353daf8d3509aed25c74811dc8..2d0f1ac73fc39d38b2601c0fa501106e1e4a71a0 100644 (file)
@@ -42,3 +42,5 @@ btrfs-fix-xattr-loss-after-power-failure.patch
 btrfs-send-fix-invalid-access-to-commit-roots-due-to-concurrent-snapshotting.patch
 btrfs-property-set-incompat-flag-if-lzo-zstd-compression-is-set.patch
 btrfs-fix-crash-when-trying-to-resume-balance-without-the-resume-flag.patch
+btrfs-split-btrfs_del_delalloc_inode-into-2-functions.patch
+btrfs-fix-delalloc-inodes-invalidation-during-transaction-abort.patch