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)
committerTheodore Ts'o <tytso@mit.edu>
Sat, 13 May 2023 22:05:04 +0000 (18:05 -0400)
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>
fs/ext4/extents_status.c

index 7bc221038c6c1041c91d9cc8d31368d813fee9bc..595abb9e7d74bb66073e00318ec54dbe4f9b7755 100644 (file)
@@ -267,14 +267,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);
@@ -293,7 +291,7 @@ out:
        }
 
        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;
@@ -931,14 +929,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;