From c6c425829c8635a243211cbc14dd34a042a7f796 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Thu, 20 Aug 2026 16:38:42 +0300 Subject: [PATCH] liblzma: LZMA2 decoder: Be more strict about compressed chunk size It wasn't really a bug, but it's good to be more strict. Side effects ("downsides" would be a too strong word): - Now the LZMA decoder has to use the slow path at the end of every LZMA2 chunk even when the read position isn't close to &in[in_size]. This doesn't matter in practice. - If a chunk header was corrupt exactly so that the compressed size is too small, now fewer bytes of uncompressed data can be recovered. Reported-by: GitHub user christos-spearbit --- src/liblzma/lzma/lzma2_decoder.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/liblzma/lzma/lzma2_decoder.c b/src/liblzma/lzma/lzma2_decoder.c index 37ab253f..164897ad 100644 --- a/src/liblzma/lzma/lzma2_decoder.c +++ b/src/liblzma/lzma/lzma2_decoder.c @@ -165,11 +165,32 @@ lzma2_decode(void *coder_ptr, lzma_dict *restrict dict, // coder->compressed_size later. const size_t in_start = *in_pos; + // LZMA2 stream ends with the end marker (0x00), so there + // must be at least one byte after this chunk. Let the + // decoder read at most one byte past the end of the chunk. + // If the decoder reads the extra byte, then the input is + // corrupt. This way we won't produce (much) junk output + // from the input bytes that are past the end of this chunk. + // The extra byte makes things simpler, because we can ignore + // uncompressed size and not think about some corner cases. + // + // NOTE: It's not a security issue (information leak) to + // pass more input to the decoder than the chunk size. + // If an attacker can modify the compressed input, then the + // attacker can modify a chunk header so that it specifies + // a too large compressed size. liblzma <= 5.8.3 didn't + // have in_limit; in_size was passed to the decoder as is. + const size_t in_limit = *in_pos + my_min(in_size - *in_pos, + coder->compressed_size + 1); + // Decode from in[] to *dict. const lzma_ret ret = coder->lzma.code(coder->lzma.coder, - dict, in, in_pos, in_size); + dict, in, in_pos, in_limit); - // Validate and update coder->compressed_size. + // Validate and update coder->compressed_size. If the input + // is corrupt, let the caller still see the newly-decoded + // output even if it is (partially) corrupt. It might allow + // users to recover a small amount of useful data. const size_t in_used = *in_pos - in_start; if (in_used > coder->compressed_size) return LZMA_DATA_ERROR;