Skip to content

Commit 314bfa4

Browse files
adam900710kdave
authored andcommitted
btrfs: lzo: Add header length check to avoid potential out-of-bounds access
James Harvey reported that some corrupted compressed extent data can lead to various kernel memory corruption. Such corrupted extent data belongs to inode with NODATASUM flags, thus data csum won't help us detecting such bug. If lucky enough, KASAN could catch it like: BUG: KASAN: slab-out-of-bounds in lzo_decompress_bio+0x384/0x7a0 [btrfs] Write of size 4096 at addr ffff8800606cb0f8 by task kworker/u16:0/2338 CPU: 3 PID: 2338 Comm: kworker/u16:0 Tainted: G O 4.17.0-rc5-custom+ torvalds#50 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 Workqueue: btrfs-endio btrfs_endio_helper [btrfs] Call Trace: dump_stack+0xc2/0x16b print_address_description+0x6a/0x270 kasan_report+0x260/0x380 memcpy+0x34/0x50 lzo_decompress_bio+0x384/0x7a0 [btrfs] end_compressed_bio_read+0x99f/0x10b0 [btrfs] bio_endio+0x32e/0x640 normal_work_helper+0x15a/0xea0 [btrfs] process_one_work+0x7e3/0x1470 worker_thread+0x1b0/0x1170 kthread+0x2db/0x390 ret_from_fork+0x22/0x40 ... The offending compressed data has the following info: Header: length 32768 (looks completely valid) Segment 0 Header: length 3472882419 (obviously out of bounds) Then when handling segment 0, since it's over the current page, we need the copy the compressed data to temporary buffer in workspace, then such large size would trigger out-of-bounds memory access, screwing up the whole kernel. Fix it by adding extra checks on header and segment headers to ensure we won't access out-of-bounds, and even checks the decompressed data won't be out-of-bounds. Reported-by: James Harvey <jamespharvey20@gmail.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Misono Tomohiro <misono.tomohiro@jp.fujitsu.com> Reviewed-by: David Sterba <dsterba@suse.com> [ updated comments ] Signed-off-by: David Sterba <dsterba@suse.com>
1 parent 2a1f7c0 commit 314bfa4

File tree

1 file changed

+26
-2
lines changed

1 file changed

+26
-2
lines changed

fs/btrfs/lzo.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
295295
unsigned long working_bytes;
296296
size_t in_len;
297297
size_t out_len;
298+
const size_t max_segment_len = lzo1x_worst_compress(PAGE_SIZE);
298299
unsigned long in_offset;
299300
unsigned long in_page_bytes_left;
300301
unsigned long tot_in;
@@ -308,10 +309,22 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
308309

309310
data_in = kmap(pages_in[0]);
310311
tot_len = read_compress_length(data_in);
312+
/*
313+
* Compressed data header check.
314+
*
315+
* The real compressed size can't exceed the maximum extent length, and
316+
* all pages should be used (whole unused page with just the segment
317+
* header is not possible). If this happens it means the compressed
318+
* extent is corrupted.
319+
*/
320+
if (tot_len > min_t(size_t, BTRFS_MAX_COMPRESSED, srclen) ||
321+
tot_len < srclen - PAGE_SIZE) {
322+
ret = -EUCLEAN;
323+
goto done;
324+
}
311325

312326
tot_in = LZO_LEN;
313327
in_offset = LZO_LEN;
314-
tot_len = min_t(size_t, srclen, tot_len);
315328
in_page_bytes_left = PAGE_SIZE - LZO_LEN;
316329

317330
tot_out = 0;
@@ -322,6 +335,17 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
322335
in_offset += LZO_LEN;
323336
tot_in += LZO_LEN;
324337

338+
/*
339+
* Segment header check.
340+
*
341+
* The segment length must not exceed the maximum LZO
342+
* compression size, nor the total compressed size.
343+
*/
344+
if (in_len > max_segment_len || tot_in + in_len > tot_len) {
345+
ret = -EUCLEAN;
346+
goto done;
347+
}
348+
325349
tot_in += in_len;
326350
working_bytes = in_len;
327351
may_late_unmap = need_unmap = false;
@@ -372,7 +396,7 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
372396
}
373397
}
374398

375-
out_len = lzo1x_worst_compress(PAGE_SIZE);
399+
out_len = max_segment_len;
376400
ret = lzo1x_decompress_safe(buf, in_len, workspace->buf,
377401
&out_len);
378402
if (need_unmap)

0 commit comments

Comments
 (0)