aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOmar Sandoval <osandov@fb.com>2017-02-10 18:03:35 -0500
committerChris Mason <clm@fb.com>2017-02-10 22:11:03 -0500
commit6e78b3f7a193546b1c00a6d084596e774f147169 (patch)
treef9cb50e4e7ad54fa71359dab4aba0fe742987e0e
parentf3c7bfbda7ce03c603b4292efddc944228dccc55 (diff)
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: 974b1adc3b10 ("btrfs: use bio iterators for the decompression handlers") Reported-by: Pat Erley <pat-lkml@erley.org> Reviewed-by: Chris Mason <clm@fb.com> Signed-off-by: Omar Sandoval <osandov@fb.com> Signed-off-by: Chris Mason <clm@fb.com> Reviewed-by: Liu Bo <bo.li.liu@oracle.com> Tested-by: Liu Bo <bo.li.liu@oracle.com>
-rw-r--r--fs/btrfs/compression.c39
1 files changed, 24 insertions, 15 deletions
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index 7f390849343b..c4444d6f439f 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -1024,6 +1024,7 @@ int btrfs_decompress_buf2page(char *buf, unsigned long buf_start,
1024 unsigned long buf_offset; 1024 unsigned long buf_offset;
1025 unsigned long current_buf_start; 1025 unsigned long current_buf_start;
1026 unsigned long start_byte; 1026 unsigned long start_byte;
1027 unsigned long prev_start_byte;
1027 unsigned long working_bytes = total_out - buf_start; 1028 unsigned long working_bytes = total_out - buf_start;
1028 unsigned long bytes; 1029 unsigned long bytes;
1029 char *kaddr; 1030 char *kaddr;
@@ -1071,26 +1072,34 @@ int btrfs_decompress_buf2page(char *buf, unsigned long buf_start,
1071 if (!bio->bi_iter.bi_size) 1072 if (!bio->bi_iter.bi_size)
1072 return 0; 1073 return 0;
1073 bvec = bio_iter_iovec(bio, bio->bi_iter); 1074 bvec = bio_iter_iovec(bio, bio->bi_iter);
1074 1075 prev_start_byte = start_byte;
1075 start_byte = page_offset(bvec.bv_page) - disk_start; 1076 start_byte = page_offset(bvec.bv_page) - disk_start;
1076 1077
1077 /* 1078 /*
1078 * make sure our new page is covered by this 1079 * We need to make sure we're only adjusting
1079 * working buffer 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.
1080 */ 1083 */
1081 if (total_out <= start_byte) 1084 if (start_byte != prev_start_byte) {
1082 return 1; 1085 /*
1086 * make sure our new page is covered by this
1087 * working buffer
1088 */
1089 if (total_out <= start_byte)
1090 return 1;
1083 1091
1084 /* 1092 /*
1085 * the next page in the biovec might not be adjacent 1093 * the next page in the biovec might not be adjacent
1086 * to the last page, but it might still be found 1094 * to the last page, but it might still be found
1087 * inside this working buffer. bump our offset pointer 1095 * inside this working buffer. bump our offset pointer
1088 */ 1096 */
1089 if (total_out > start_byte && 1097 if (total_out > start_byte &&
1090 current_buf_start < start_byte) { 1098 current_buf_start < start_byte) {
1091 buf_offset = start_byte - buf_start; 1099 buf_offset = start_byte - buf_start;
1092 working_bytes = total_out - start_byte; 1100 working_bytes = total_out - start_byte;
1093 current_buf_start = buf_start + buf_offset; 1101 current_buf_start = buf_start + buf_offset;
1102 }
1094 } 1103 }
1095 } 1104 }
1096 1105