diff options
Diffstat (limited to 'fs/btrfs/lzo.c')
| -rw-r--r-- | fs/btrfs/lzo.c | 76 | 
1 files changed, 73 insertions, 3 deletions
diff --git a/fs/btrfs/lzo.c b/fs/btrfs/lzo.c index 0667ea07f766..b6a4cc178bee 100644 --- a/fs/btrfs/lzo.c +++ b/fs/btrfs/lzo.c @@ -17,6 +17,43 @@  #define LZO_LEN	4 +/* + * Btrfs LZO compression format + * + * Regular and inlined LZO compressed data extents consist of: + * + * 1.  Header + *     Fixed size. LZO_LEN (4) bytes long, LE32. + *     Records the total size (including the header) of compressed data. + * + * 2.  Segment(s) + *     Variable size. Each segment includes one segment header, followd by data + *     payload. + *     One regular LZO compressed extent can have one or more segments. + *     For inlined LZO compressed extent, only one segment is allowed. + *     One segment represents at most one page of uncompressed data. + * + * 2.1 Segment header + *     Fixed size. LZO_LEN (4) bytes long, LE32. + *     Records the total size of the segment (not including the header). + *     Segment header never crosses page boundary, thus it's possible to + *     have at most 3 padding zeros at the end of the page. + * + * 2.2 Data Payload + *     Variable size. Size up limit should be lzo1x_worst_compress(PAGE_SIZE) + *     which is 4419 for a 4KiB page. + * + * Example: + * Page 1: + *          0     0x2   0x4   0x6   0x8   0xa   0xc   0xe     0x10 + * 0x0000   |  Header   | SegHdr 01 | Data payload 01 ...     | + * ... + * 0x0ff0   | SegHdr  N | Data payload  N     ...          |00| + *                                                          ^^ padding zeros + * Page 2: + * 0x1000   | SegHdr N+1| Data payload N+1 ...                | + */ +  struct workspace {  	void *mem;  	void *buf;	/* where decompressed data goes */ @@ -258,6 +295,7 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)  	unsigned long working_bytes;  	size_t in_len;  	size_t out_len; +	const size_t max_segment_len = lzo1x_worst_compress(PAGE_SIZE);  	unsigned long in_offset;  	unsigned long in_page_bytes_left;  	unsigned long tot_in; @@ -271,10 +309,22 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)  	data_in = kmap(pages_in[0]);  	tot_len = read_compress_length(data_in); +	/* +	 * Compressed data header check. +	 * +	 * The real compressed size can't exceed the maximum extent length, and +	 * all pages should be used (whole unused page with just the segment +	 * header is not possible).  If this happens it means the compressed +	 * extent is corrupted. +	 */ +	if (tot_len > min_t(size_t, BTRFS_MAX_COMPRESSED, srclen) || +	    tot_len < srclen - PAGE_SIZE) { +		ret = -EUCLEAN; +		goto done; +	}  	tot_in = LZO_LEN;  	in_offset = LZO_LEN; -	tot_len = min_t(size_t, srclen, tot_len);  	in_page_bytes_left = PAGE_SIZE - LZO_LEN;  	tot_out = 0; @@ -285,6 +335,17 @@ static int lzo_decompress_bio(struct list_head *ws, struct compressed_bio *cb)  		in_offset += LZO_LEN;  		tot_in += LZO_LEN; +		/* +		 * Segment header check. +		 * +		 * The segment length must not exceed the maximum LZO +		 * compression size, nor the total compressed size. +		 */ +		if (in_len > max_segment_len || tot_in + in_len > tot_len) { +			ret = -EUCLEAN; +			goto done; +		} +  		tot_in += in_len;  		working_bytes = in_len;  		may_late_unmap = need_unmap = false; @@ -335,7 +396,7 @@ cont:  			}  		} -		out_len = lzo1x_worst_compress(PAGE_SIZE); +		out_len = max_segment_len;  		ret = lzo1x_decompress_safe(buf, in_len, workspace->buf,  					    &out_len);  		if (need_unmap) @@ -369,15 +430,24 @@ static int lzo_decompress(struct list_head *ws, unsigned char *data_in,  	struct workspace *workspace = list_entry(ws, struct workspace, list);  	size_t in_len;  	size_t out_len; +	size_t max_segment_len = lzo1x_worst_compress(PAGE_SIZE);  	int ret = 0;  	char *kaddr;  	unsigned long bytes; -	BUG_ON(srclen < LZO_LEN); +	if (srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2) +		return -EUCLEAN; +	in_len = read_compress_length(data_in); +	if (in_len != srclen) +		return -EUCLEAN;  	data_in += LZO_LEN;  	in_len = read_compress_length(data_in); +	if (in_len != srclen - LZO_LEN * 2) { +		ret = -EUCLEAN; +		goto out; +	}  	data_in += LZO_LEN;  	out_len = PAGE_SIZE;  |