Commit 1a932ef4 authored by Liu Bo's avatar Liu Bo Committed by David Sterba

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: <stable@vger.kernel.org> v3.12+
Fixes: 703c88e0 ("Btrfs: fix tracking of orphan inode count")
Signed-off-by: default avatarLiu Bo <bo.li.liu@oracle.com>
Reviewed-by: default avatarJosef Bacik <jbacik@fb.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent e8f1bc14
...@@ -3387,6 +3387,11 @@ int btrfs_orphan_add(struct btrfs_trans_handle *trans, ...@@ -3387,6 +3387,11 @@ int btrfs_orphan_add(struct btrfs_trans_handle *trans,
ret = btrfs_orphan_reserve_metadata(trans, inode); ret = btrfs_orphan_reserve_metadata(trans, inode);
ASSERT(!ret); ASSERT(!ret);
if (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); atomic_dec(&root->orphan_inodes);
clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED, clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED,
&inode->runtime_flags); &inode->runtime_flags);
...@@ -3401,12 +3406,17 @@ int btrfs_orphan_add(struct btrfs_trans_handle *trans, ...@@ -3401,12 +3406,17 @@ int btrfs_orphan_add(struct btrfs_trans_handle *trans,
if (insert >= 1) { if (insert >= 1) {
ret = btrfs_insert_orphan_item(trans, root, btrfs_ino(inode)); ret = btrfs_insert_orphan_item(trans, root, btrfs_ino(inode));
if (ret) { if (ret) {
atomic_dec(&root->orphan_inodes);
if (reserve) { if (reserve) {
clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED, clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED,
&inode->runtime_flags); &inode->runtime_flags);
btrfs_orphan_release_metadata(inode); 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) { if (ret != -EEXIST) {
clear_bit(BTRFS_INODE_HAS_ORPHAN_ITEM, clear_bit(BTRFS_INODE_HAS_ORPHAN_ITEM,
&inode->runtime_flags); &inode->runtime_flags);
...@@ -3438,28 +3448,26 @@ static int btrfs_orphan_del(struct btrfs_trans_handle *trans, ...@@ -3438,28 +3448,26 @@ static int btrfs_orphan_del(struct btrfs_trans_handle *trans,
{ {
struct btrfs_root *root = inode->root; struct btrfs_root *root = inode->root;
int delete_item = 0; int delete_item = 0;
int release_rsv = 0;
int ret = 0; int ret = 0;
spin_lock(&root->orphan_lock);
if (test_and_clear_bit(BTRFS_INODE_HAS_ORPHAN_ITEM, if (test_and_clear_bit(BTRFS_INODE_HAS_ORPHAN_ITEM,
&inode->runtime_flags)) &inode->runtime_flags))
delete_item = 1; 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, if (test_and_clear_bit(BTRFS_INODE_ORPHAN_META_RESERVED,
&inode->runtime_flags)) &inode->runtime_flags))
release_rsv = 1; btrfs_orphan_release_metadata(inode);
spin_unlock(&root->orphan_lock);
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); atomic_dec(&root->orphan_inodes);
if (trans)
ret = btrfs_del_orphan_item(trans, root,
btrfs_ino(inode));
}
if (release_rsv)
btrfs_orphan_release_metadata(inode);
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment