Btrfs: fix use-after-free on root->orphan_block_rsv

"Linux Kernel Mailing List" <[email protected]> Fri, 16 Feb 2018 17:44:22 +0000 (UTC)
Newsgroups gmane.linux.kernel.commits.head
Message-ID <[email protected]>
Web:        https://git.kernel.org/torvalds/c/1a932ef4e47984dee227834667b5ff5a334e4805
Commit:     1a932ef4e47984dee227834667b5ff5a334e4805
Parent:     e8f1bc1493855e32b7a2a019decc3c353d94daf6
Refname:    refs/heads/master
Author:     Liu Bo <[email protected]>
AuthorDate: Thu Jan 25 11:02:54 2018 -0700
Committer:  David Sterba <[email protected]>
CommitDate: Fri Feb 2 16:24:40 2018 +0100

    Btrfs: fix use-after-free on root->orphan_block_rsv
    
    I got these from running generic/475,
    
    WARNING: CPU: 0 PID: 26384 at fs/btrfs/inode.c:3326 btrfs_orphan_commit_root+0x1ac/0x2b0 [btrfs]
    BUG: unable to handle kernel NULL pointer dereference at 0000000000000010
    IP: btrfs_block_rsv_release+0x1c/0x70 [btrfs]
    Call Trace:
      btrfs_orphan_release_metadata+0x9f/0x200 [btrfs]
      btrfs_orphan_del+0x10d/0x170 [btrfs]
      btrfs_setattr+0x500/0x640 [btrfs]
      notify_change+0x7ae/0x870
      do_truncate+0xca/0x130
      vfs_truncate+0x2ee/0x3d0
      do_sys_truncate+0xaf/0xf0
      SyS_truncate+0xe/0x10
      entry_SYSCALL_64_fastpath+0x1f/0x96
    
    The race is between btrfs_orphan_commit_root and btrfs_orphan_del,
            t1                                        t2
    btrfs_orphan_commit_root                     btrfs_orphan_del
       spin_lock
       check (&root->orphan_inodes)
       root->orphan_block_rsv = NULL;
       spin_unlock
                                                 atomic_dec(&root->orphan_inodes);
                                                 access root->orphan_block_rsv
    
    Accessing root->orphan_block_rsv must be done before decreasing
    root->orphan_inodes.
    
    cc: <[email protected]> v3.12+
    Fixes: 703c88e03524 ("Btrfs: fix tracking of orphan inode count")
    Signed-off-by: Liu Bo <[email protected]>
    Reviewed-by: Josef Bacik <[email protected]>
    Signed-off-by: David Sterba <[email protected]>
---
 fs/btrfs/inode.c | 34 +++++++++++++++++++++-------------
 1 file changed, 21 insertions(+), 13 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 44a152d8f32f..29b491328f4e 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3387,6 +3387,11 @@ int btrfs_orphan_add(struct btrfs_trans_handle *trans,
 		ret = btrfs_orphan_reserve_metadata(trans, inode);
 		ASSERT(!ret);
 		if (ret) {
+			/*
+			 * dec doesn't need spin_lock as ->orphan_block_rsv
+			 * would be released only if ->orphan_inodes is
+			 * zero.
+			 */
 			atomic_dec(&root->orphan_inodes);
 			clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED,
 				  &inode->runtime_flags);
@@ -3401,12 +3406,17 @@ int btrfs_orphan_add(struct btrfs_trans_handle *trans,
 	if (insert >= 1) {
 		ret = btrfs_insert_orphan_item(trans, root, btrfs_ino(inode));
 		if (ret) {
-			atomic_dec(&root->orphan_inodes);
 			if (reserve) {
 				clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED,
 					  &inode->runtime_flags);
 				btrfs_orphan_release_metadata(inode);
 			}
+			/*
+			 * btrfs_orphan_commit_root may race with us and set
+			 * ->orphan_block_rsv to zero, in order to avoid that,
+			 * decrease ->orphan_inodes after everything is done.
+			 */
+			atomic_dec(&root->orphan_inodes);
 			if (ret != -EEXIST) {
 				clear_bit(BTRFS_INODE_HAS_ORPHAN_ITEM,
 					  &inode->runtime_flags);
@@ -3438,28 +3448,26 @@ static int btrfs_orphan_del(struct btrfs_trans_handle *trans,
 {
 	struct btrfs_root *root = inode->root;
 	int delete_item = 0;
-	int release_rsv = 0;
 	int ret = 0;
 
-	spin_lock(&root->orphan_lock);
 	if (test_and_clear_bit(BTRFS_INODE_HAS_ORPHAN_ITEM,
 			       &inode->runtime_flags))
 		delete_item = 1;
 
+	if (delete_item && trans)
+		ret = btrfs_del_orphan_item(trans, root, btrfs_ino(inode));
+
 	if (test_and_clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED,
 			       &inode->runtime_flags))
-		release_rsv = 1;
-	spin_unlock(&root->orphan_lock);
+		btrfs_orphan_release_metadata(inode);
 
-	if (delete_item) {
+	/*
+	 * btrfs_orphan_commit_root may race with us and set ->orphan_block_rsv
+	 * to zero, in order to avoid that, decrease ->orphan_inodes after
+	 * everything is done.
+	 */
+	if (delete_item)
 		atomic_dec(&root->orphan_inodes);
-		if (trans)
-			ret = btrfs_del_orphan_item(trans, root,
-						    btrfs_ino(inode));
-	}