summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2018-10-12 13:02:48 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-11-13 11:15:17 -0800
commitbf005ed0fbddd6a5e3a2c1c3b222c4104a9819b5 (patch)
treedbccc8f89e4913abeb908d0d91dfca13691782d9
parentc8bac97db4639b958af13cc41ebcaea3297d4e72 (diff)
downloadlinux-stable-bf005ed0fbddd6a5e3a2c1c3b222c4104a9819b5.tar.gz
linux-stable-bf005ed0fbddd6a5e3a2c1c3b222c4104a9819b5.tar.bz2
linux-stable-bf005ed0fbddd6a5e3a2c1c3b222c4104a9819b5.zip
Btrfs: fix use-after-free during inode eviction
commit 421f0922a2cfb0c75acd9746454aaa576c711a65 upstream. At inode.c:evict_inode_truncate_pages(), when we iterate over the inode's extent states, we access an extent state record's "state" field after we unlocked the inode's io tree lock. This can lead to a use-after-free issue because after we unlock the io tree that extent state record might have been freed due to being merged into another adjacent extent state record (a previous inflight bio for a read operation finished in the meanwhile which unlocked a range in the io tree and cause a merge of extent state records, as explained in the comment before the while loop added in commit 6ca0709756710 ("Btrfs: fix hang during inode eviction due to concurrent readahead")). Fix this by keeping a copy of the extent state's flags in a local variable and using it after unlocking the io tree. Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=201189 Fixes: b9d0b38928e2 ("btrfs: Add handler for invalidate page") CC: stable@vger.kernel.org # 4.4+ Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--fs/btrfs/inode.c4
1 files changed, 3 insertions, 1 deletions
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 39edb4b71fa9..90568a21fa77 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -5335,11 +5335,13 @@ static void evict_inode_truncate_pages(struct inode *inode)
struct extent_state *cached_state = NULL;
u64 start;
u64 end;
+ unsigned state_flags;
node = rb_first(&io_tree->state);
state = rb_entry(node, struct extent_state, rb_node);
start = state->start;
end = state->end;
+ state_flags = state->state;
spin_unlock(&io_tree->lock);
lock_extent_bits(io_tree, start, end, &cached_state);
@@ -5352,7 +5354,7 @@ static void evict_inode_truncate_pages(struct inode *inode)
*
* Note, end is the bytenr of last byte, so we need + 1 here.
*/
- if (state->state & EXTENT_DELALLOC)
+ if (state_flags & EXTENT_DELALLOC)
btrfs_qgroup_free_data(inode, NULL, start, end - start + 1);
clear_extent_bit(io_tree, start, end,