Skip to content

Commit 6e78b3f

Browse files
osandovmasoncl
authored andcommitted
Btrfs: fix btrfs_decompress_buf2page()
If btrfs_decompress_buf2page() is handed a bio with its page in the middle of the working buffer, then we adjust the offset into the working buffer. After we copy into the bio, we advance the iterator by the number of bytes we copied. Then, we have some logic to handle the case of discontiguous pages and adjust the offset into the working buffer again. However, if we didn't advance the bio to a new page, we may enter this case in error, essentially repeating the adjustment that we already made when we entered the function. The end result is bogus data in the bio. Previously, we only checked for this case when we advanced to a new page, but the conversion to bio iterators changed that. This restores the old, correct behavior. A case I saw when testing with zlib was: buf_start = 42769 total_out = 46865 working_bytes = total_out - buf_start = 4096 start_byte = 45056 The condition (total_out > start_byte && buf_start < start_byte) is true, so we adjust the offset: buf_offset = start_byte - buf_start = 2287 working_bytes -= buf_offset = 1809 current_buf_start = buf_start = 42769 Then, we copy bytes = min(bvec.bv_len, PAGE_SIZE - buf_offset, working_bytes) = 1809 buf_offset += bytes = 4096 working_bytes -= bytes = 0 current_buf_start += bytes = 44578 After bio_advance(), we are still in the same page, so start_byte is the same. Then, we check (total_out > start_byte && current_buf_start < start_byte), which is true! So, we adjust the values again: buf_offset = start_byte - buf_start = 2287 working_bytes = total_out - start_byte = 1809 current_buf_start = buf_start + buf_offset = 45056 But note that working_bytes was already zero before this, so we should have stopped copying. Fixes: 974b1ad ("btrfs: use bio iterators for the decompression handlers") Reported-by: Pat Erley <[email protected]> Reviewed-by: Chris Mason <[email protected]> Signed-off-by: Omar Sandoval <[email protected]> Signed-off-by: Chris Mason <[email protected]> Reviewed-by: Liu Bo <[email protected]> Tested-by: Liu Bo <[email protected]>
1 parent f3c7bfb commit 6e78b3f

File tree

1 file changed

+24
-15
lines changed

1 file changed

+24
-15
lines changed

fs/btrfs/compression.c

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,7 @@ int btrfs_decompress_buf2page(char *buf, unsigned long buf_start,
10241024
unsigned long buf_offset;
10251025
unsigned long current_buf_start;
10261026
unsigned long start_byte;
1027+
unsigned long prev_start_byte;
10271028
unsigned long working_bytes = total_out - buf_start;
10281029
unsigned long bytes;
10291030
char *kaddr;
@@ -1071,26 +1072,34 @@ int btrfs_decompress_buf2page(char *buf, unsigned long buf_start,
10711072
if (!bio->bi_iter.bi_size)
10721073
return 0;
10731074
bvec = bio_iter_iovec(bio, bio->bi_iter);
1074-
1075+
prev_start_byte = start_byte;
10751076
start_byte = page_offset(bvec.bv_page) - disk_start;
10761077

10771078
/*
1078-
* make sure our new page is covered by this
1079-
* working buffer
1079+
* We need to make sure we're only adjusting
1080+
* our offset into compression working buffer when
1081+
* we're switching pages. Otherwise we can incorrectly
1082+
* keep copying when we were actually done.
10801083
*/
1081-
if (total_out <= start_byte)
1082-
return 1;
1084+
if (start_byte != prev_start_byte) {
1085+
/*
1086+
* make sure our new page is covered by this
1087+
* working buffer
1088+
*/
1089+
if (total_out <= start_byte)
1090+
return 1;
10831091

1084-
/*
1085-
* the next page in the biovec might not be adjacent
1086-
* to the last page, but it might still be found
1087-
* inside this working buffer. bump our offset pointer
1088-
*/
1089-
if (total_out > start_byte &&
1090-
current_buf_start < start_byte) {
1091-
buf_offset = start_byte - buf_start;
1092-
working_bytes = total_out - start_byte;
1093-
current_buf_start = buf_start + buf_offset;
1092+
/*
1093+
* the next page in the biovec might not be adjacent
1094+
* to the last page, but it might still be found
1095+
* inside this working buffer. bump our offset pointer
1096+
*/
1097+
if (total_out > start_byte &&
1098+
current_buf_start < start_byte) {
1099+
buf_offset = start_byte - buf_start;
1100+
working_bytes = total_out - start_byte;
1101+
current_buf_start = buf_start + buf_offset;
1102+
}
10941103
}
10951104
}
10961105

0 commit comments

Comments
 (0)