Skip to content

Commit a514983

Browse files
adam900710kdave
authored andcommitted
btrfs: fix corruption reading compressed range when block size is smaller than page size
[BUG] With 64K page size (aarch64 with 64K page size config) and 4K btrfs block size, the following workload can easily lead to a corrupted read: mkfs.btrfs -f -s 4k $dev > /dev/null mount -o compress $dev $mnt xfs_io -f -c "pwrite -S 0xff 0 64k" $mnt/base > /dev/null echo "correct result:" od -Ad -t x1 $mnt/base xfs_io -f -c "reflink $mnt/base 32k 0 32k" \ -c "reflink $mnt/base 0 32k 32k" \ -c "pwrite -S 0xff 60k 4k" $mnt/new > /dev/null echo "incorrect result:" od -Ad -t x1 $mnt/new umount $mnt This shows the following result: correct result: 0000000 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff * 0065536 incorrect result: 0000000 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff * 0032768 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 0061440 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff * 0065536 Notice the zero in the range [32K, 60K), which is incorrect. [CAUSE] With extra trace printk, it shows the following events during od: (some unrelated info removed like CPU and context) od-3457 btrfs_do_readpage: enter r/i=5/258 folio=0(65536) prev_em_start=0000000000000000 The "r/i" is indicating the root and inode number. In our case the file "new" is using ino 258 from fs tree (root 5). Here notice the @prev_em_start pointer is NULL. This means the btrfs_do_readpage() is called from btrfs_read_folio(), not from btrfs_readahead(). od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=0 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=4096 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=8192 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=12288 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=16384 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=20480 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=24576 got em start=0 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=28672 got em start=0 len=32768 These above 32K blocks will be read from the first half of the compressed data extent. od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=32768 got em start=32768 len=32768 Note here there is no btrfs_submit_compressed_read() call. Which is incorrect now. Although both extent maps at 0 and 32K are pointing to the same compressed data, their offsets are different thus can not be merged into the same read. So this means the compressed data read merge check is doing something wrong. od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=36864 got em start=32768 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=40960 got em start=32768 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=45056 got em start=32768 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=49152 got em start=32768 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=53248 got em start=32768 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=57344 got em start=32768 len=32768 od-3457 btrfs_do_readpage: r/i=5/258 folio=0(65536) cur=61440 skip uptodate od-3457 btrfs_submit_compressed_read: cb orig_bio: file off=0 len=61440 The function btrfs_submit_compressed_read() is only called at the end of folio read. The compressed bio will only have an extent map of range [0, 32K), but the original bio passed in is for the whole 64K folio. This will cause the decompression part to only fill the first 32K, leaving the rest untouched (aka, filled with zero). This incorrect compressed read merge leads to the above data corruption. There were similar problems that happened in the past, commit 808f80b ("Btrfs: update fix for read corruption of compressed and shared extents") is doing pretty much the same fix for readahead. But that's back to 2015, where btrfs still only supports bs (block size) == ps (page size) cases. This means btrfs_do_readpage() only needs to handle a folio which contains exactly one block. Only btrfs_readahead() can lead to a read covering multiple blocks. Thus only btrfs_readahead() passes a non-NULL @prev_em_start pointer. With v5.15 kernel btrfs introduced bs < ps support. This breaks the above assumption that a folio can only contain one block. Now btrfs_read_folio() can also read multiple blocks in one go. But btrfs_read_folio() doesn't pass a @prev_em_start pointer, thus the existing bio force submission check will never be triggered. In theory, this can also happen for btrfs with large folios, but since large folio is still experimental, we don't need to bother it, thus only bs < ps support is affected for now. [FIX] Instead of passing @prev_em_start to do the proper compressed extent check, introduce one new member, btrfs_bio_ctrl::last_em_start, so that the existing bio force submission logic will always be triggered. CC: [email protected] # 5.15+ Reviewed-by: Filipe Manana <[email protected]> Signed-off-by: Qu Wenruo <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 4e8beae commit a514983

File tree

1 file changed

+30
-10
lines changed

1 file changed

+30
-10
lines changed

fs/btrfs/extent_io.c

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@ struct btrfs_bio_ctrl {
131131
*/
132132
unsigned long submit_bitmap;
133133
struct readahead_control *ractl;
134+
135+
/*
136+
* The start offset of the last used extent map by a read operation.
137+
*
138+
* This is for proper compressed read merge.
139+
* U64_MAX means we are starting the read and have made no progress yet.
140+
*
141+
* The current btrfs_bio_is_contig() only uses disk_bytenr as
142+
* the condition to check if the read can be merged with previous
143+
* bio, which is not correct. E.g. two file extents pointing to the
144+
* same extent but with different offset.
145+
*
146+
* So here we need to do extra checks to only merge reads that are
147+
* covered by the same extent map.
148+
* Just extent_map::start will be enough, as they are unique
149+
* inside the same inode.
150+
*/
151+
u64 last_em_start;
134152
};
135153

136154
/*
@@ -965,7 +983,7 @@ static void btrfs_readahead_expand(struct readahead_control *ractl,
965983
* return 0 on success, otherwise return error
966984
*/
967985
static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
968-
struct btrfs_bio_ctrl *bio_ctrl, u64 *prev_em_start)
986+
struct btrfs_bio_ctrl *bio_ctrl)
969987
{
970988
struct inode *inode = folio->mapping->host;
971989
struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
@@ -1076,12 +1094,11 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
10761094
* non-optimal behavior (submitting 2 bios for the same extent).
10771095
*/
10781096
if (compress_type != BTRFS_COMPRESS_NONE &&
1079-
prev_em_start && *prev_em_start != (u64)-1 &&
1080-
*prev_em_start != em->start)
1097+
bio_ctrl->last_em_start != U64_MAX &&
1098+
bio_ctrl->last_em_start != em->start)
10811099
force_bio_submit = true;
10821100

1083-
if (prev_em_start)
1084-
*prev_em_start = em->start;
1101+
bio_ctrl->last_em_start = em->start;
10851102

10861103
em_gen = em->generation;
10871104
btrfs_free_extent_map(em);
@@ -1296,12 +1313,15 @@ int btrfs_read_folio(struct file *file, struct folio *folio)
12961313
const u64 start = folio_pos(folio);
12971314
const u64 end = start + folio_size(folio) - 1;
12981315
struct extent_state *cached_state = NULL;
1299-
struct btrfs_bio_ctrl bio_ctrl = { .opf = REQ_OP_READ };
1316+
struct btrfs_bio_ctrl bio_ctrl = {
1317+
.opf = REQ_OP_READ,
1318+
.last_em_start = U64_MAX,
1319+
};
13001320
struct extent_map *em_cached = NULL;
13011321
int ret;
13021322

13031323
lock_extents_for_read(inode, start, end, &cached_state);
1304-
ret = btrfs_do_readpage(folio, &em_cached, &bio_ctrl, NULL);
1324+
ret = btrfs_do_readpage(folio, &em_cached, &bio_ctrl);
13051325
btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state);
13061326

13071327
btrfs_free_extent_map(em_cached);
@@ -2641,20 +2661,20 @@ void btrfs_readahead(struct readahead_control *rac)
26412661
{
26422662
struct btrfs_bio_ctrl bio_ctrl = {
26432663
.opf = REQ_OP_READ | REQ_RAHEAD,
2644-
.ractl = rac
2664+
.ractl = rac,
2665+
.last_em_start = U64_MAX,
26452666
};
26462667
struct folio *folio;
26472668
struct btrfs_inode *inode = BTRFS_I(rac->mapping->host);
26482669
const u64 start = readahead_pos(rac);
26492670
const u64 end = start + readahead_length(rac) - 1;
26502671
struct extent_state *cached_state = NULL;
26512672
struct extent_map *em_cached = NULL;
2652-
u64 prev_em_start = (u64)-1;
26532673

26542674
lock_extents_for_read(inode, start, end, &cached_state);
26552675

26562676
while ((folio = readahead_folio(rac)) != NULL)
2657-
btrfs_do_readpage(folio, &em_cached, &bio_ctrl, &prev_em_start);
2677+
btrfs_do_readpage(folio, &em_cached, &bio_ctrl);
26582678

26592679
btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state);
26602680

0 commit comments

Comments
 (0)