]> git.itanic.dy.fi Git - linux-stable/commitdiff
ext4: fix data races when using cached status extents
authorJan Kara <jack@suse.cz>
Thu, 4 May 2023 12:55:24 +0000 (14:55 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 17 May 2023 09:53:57 +0000 (11:53 +0200)
commit 492888df0c7b42fc0843631168b0021bc4caee84 upstream.

When using cached extent stored in extent status tree in tree->cache_es
another process holding ei->i_es_lock for reading can be racing with us
setting new value of tree->cache_es. If the compiler would decide to
refetch tree->cache_es at an unfortunate moment, it could result in a
bogus in_range() check. Fix the possible race by using READ_ONCE() when
using tree->cache_es only under ei->i_es_lock for reading.

Cc: stable@kernel.org
Reported-by: syzbot+4a03518df1e31b537066@syzkaller.appspotmail.com
Link: https://lore.kernel.org/all/000000000000d3b33905fa0fd4a6@google.com
Suggested-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20230504125524.10802-1-jack@suse.cz
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ext4/extents_status.c

index 7ada374ff27d742ab64429c804c65910ef75c574..44e83521bfded537413b346e2090dd2a8d963587 100644 (file)
@@ -269,14 +269,12 @@ static void __es_find_extent_range(struct inode *inode,
 
        /* see if the extent has been cached */
        es->es_lblk = es->es_len = es->es_pblk = 0;
-       if (tree->cache_es) {
-               es1 = tree->cache_es;
-               if (in_range(lblk, es1->es_lblk, es1->es_len)) {
-                       es_debug("%u cached by [%u/%u) %llu %x\n",
-                                lblk, es1->es_lblk, es1->es_len,
-                                ext4_es_pblock(es1), ext4_es_status(es1));
-                       goto out;
-               }
+       es1 = READ_ONCE(tree->cache_es);
+       if (es1 && in_range(lblk, es1->es_lblk, es1->es_len)) {
+               es_debug("%u cached by [%u/%u) %llu %x\n",
+                        lblk, es1->es_lblk, es1->es_len,
+                        ext4_es_pblock(es1), ext4_es_status(es1));
+               goto out;
        }
 
        es1 = __es_tree_search(&tree->root, lblk);
@@ -295,7 +293,7 @@ static void __es_find_extent_range(struct inode *inode,
        }
 
        if (es1 && matching_fn(es1)) {
-               tree->cache_es = es1;
+               WRITE_ONCE(tree->cache_es, es1);
                es->es_lblk = es1->es_lblk;
                es->es_len = es1->es_len;
                es->es_pblk = es1->es_pblk;
@@ -933,14 +931,12 @@ int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk,
 
        /* find extent in cache firstly */
        es->es_lblk = es->es_len = es->es_pblk = 0;
-       if (tree->cache_es) {
-               es1 = tree->cache_es;
-               if (in_range(lblk, es1->es_lblk, es1->es_len)) {
-                       es_debug("%u cached by [%u/%u)\n",
-                                lblk, es1->es_lblk, es1->es_len);
-                       found = 1;
-                       goto out;
-               }
+       es1 = READ_ONCE(tree->cache_es);
+       if (es1 && in_range(lblk, es1->es_lblk, es1->es_len)) {
+               es_debug("%u cached by [%u/%u)\n",
+                        lblk, es1->es_lblk, es1->es_len);
+               found = 1;
+               goto out;
        }
 
        node = tree->root.rb_node;