Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit ef1e682

Browse files
tavianatorkdave
authored andcommitted
btrfs: fix race in read_extent_buffer_pages()
There are reports from tree-checker that detects corrupted nodes, without any obvious pattern so possibly an overwrite in memory. After some debugging it turns out there's a race when reading an extent buffer the uptodate status can be missed. To prevent concurrent reads for the same extent buffer, read_extent_buffer_pages() performs these checks: /* (1) */ if (test_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags)) return 0; /* (2) */ if (test_and_set_bit(EXTENT_BUFFER_READING, &eb->bflags)) goto done; At this point, it seems safe to start the actual read operation. Once that completes, end_bbio_meta_read() does /* (3) */ set_extent_buffer_uptodate(eb); /* (4) */ clear_bit(EXTENT_BUFFER_READING, &eb->bflags); Normally, this is enough to ensure only one read happens, and all other callers wait for it to finish before returning. Unfortunately, there is a racey interleaving: Thread A | Thread B | Thread C ---------+----------+--------- (1) | | | (1) | (2) | | (3) | | (4) | | | (2) | | | (1) When this happens, thread B kicks of an unnecessary read. Worse, thread C will see UPTODATE set and return immediately, while the read from thread B is still in progress. This race could result in tree-checker errors like this as the extent buffer is concurrently modified: BTRFS critical (device dm-0): corrupted node, root=256 block=8550954455682405139 owner mismatch, have 11858205567642294356 expect [256, 18446744073709551360] Fix it by testing UPTODATE again after setting the READING bit, and if it's been set, skip the unnecessary read. Fixes: d7172f5 ("btrfs: use per-buffer locking for extent_buffer reading") Link: https://lore.kernel.org/linux-btrfs/CAHk-=whNdMaN9ntZ47XRKP6DBes2E5w7fi-0U3H2+PS18p+Pzw@mail.gmail.com/ Link: https://lore.kernel.org/linux-btrfs/f51a6d5d7432455a6a858d51b49ecac183e0bbc9.1706312914.git.wqu@suse.com/ Link: https://lore.kernel.org/linux-btrfs/c7241ea4-fcc6-48d2-98c8-b5ea790d6c89@gmx.com/ CC: stable@vger.kernel.org # 6.5+ Reviewed-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Tavian Barnes <tavianator@tavianator.com> Reviewed-by: David Sterba <dsterba@suse.com> [ minor update of changelog ] Signed-off-by: David Sterba <dsterba@suse.com>
1 parent 2f1aeab commit ef1e682

File tree

1 file changed

+13
-0
lines changed

1 file changed

+13
-0
lines changed

fs/btrfs/extent_io.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4333,6 +4333,19 @@ int read_extent_buffer_pages(struct extent_buffer *eb, int wait, int mirror_num,
43334333
if (test_and_set_bit(EXTENT_BUFFER_READING, &eb->bflags))
43344334
goto done;
43354335

4336+
/*
4337+
* Between the initial test_bit(EXTENT_BUFFER_UPTODATE) and the above
4338+
* test_and_set_bit(EXTENT_BUFFER_READING), someone else could have
4339+
* started and finished reading the same eb. In this case, UPTODATE
4340+
* will now be set, and we shouldn't read it in again.
4341+
*/
4342+
if (unlikely(test_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags))) {
4343+
clear_bit(EXTENT_BUFFER_READING, &eb->bflags);
4344+
smp_mb__after_atomic();
4345+
wake_up_bit(&eb->bflags, EXTENT_BUFFER_READING);
4346+
return 0;
4347+
}
4348+
43364349
clear_bit(EXTENT_BUFFER_READ_ERR, &eb->bflags);
43374350
eb->read_mirror = 0;
43384351
check_buffer_tree_ref(eb);

0 commit comments

Comments
 (0)