diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs index c5e8b4ec..ec427519 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs @@ -146,11 +146,8 @@ public partial class LzmaStream await _inputStream! .ReadExactAsync(headerBuffer, 0, 1, cancellationToken) .ConfigureAwait(false); - Properties[0] = headerBuffer[0]; _inputPosition++; - - _decoder ??= new Decoder(); - _decoder.SetDecoderProperties(Properties); + SetChunkProperties(headerBuffer[0]); } else if (_needProps) { diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs index 615ea7d6..833037bc 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -547,6 +547,21 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable return value; } + private void SetChunkProperties(byte properties) + { + // A properties byte encodes (pb * 5 + lp) * 9 + lc with pb <= 4; a larger value is + // corrupt stream data, not an invalid caller argument. + if (properties >= 9 * 5 * 5) + { + throw new DataErrorException(); + } + Properties[0] = properties; + + // Properties initialization resets every probability and match-state field. + _decoder ??= new Decoder(); + _decoder.SetDecoderProperties(Properties); + } + private void DecodeChunkHeader() { var control = ReadChunkHeaderByte(); @@ -585,11 +600,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable if (control >= 0xC0) { _needProps = false; - Properties[0] = (byte)ReadChunkHeaderByte(); - - // Properties initialization resets every probability and match-state field. - _decoder ??= new Decoder(); - _decoder.SetDecoderProperties(Properties); + SetChunkProperties((byte)ReadChunkHeaderByte()); } else if (_needProps) { diff --git a/tests/SharpCompress.Test/Streams/LzmaDecoderSafetyTests.cs b/tests/SharpCompress.Test/Streams/LzmaDecoderSafetyTests.cs index fcc55cef..0f157ed1 100644 --- a/tests/SharpCompress.Test/Streams/LzmaDecoderSafetyTests.cs +++ b/tests/SharpCompress.Test/Streams/LzmaDecoderSafetyTests.cs @@ -78,6 +78,34 @@ public class LzmaDecoderSafetyTests }); } + // LZMA2 chunk with dictionary and properties reset whose properties byte encodes pb > 4. + private static readonly byte[] InvalidChunkProperties = { 0xE0, 0, 0, 0, 4, 0xFF, 0, 0, 0, 0 }; + + [Fact] + public void Lzma2InvalidChunkPropertiesThrowsDataError() + { + using var input = new MemoryStream(InvalidChunkProperties); + using var stream = LzmaStream.Create(new byte[] { 0 }, input, leaveOpen: true); + Assert.Throws(() => stream.ReadByte()); + } + + [Fact] + public async Task AsyncLzma2InvalidChunkPropertiesThrowsDataError() + { + using var input = new MemoryStream(InvalidChunkProperties); + using var stream = await LzmaStream.CreateAsync( + new byte[] { 0 }, + input, + -1, + -1, + leaveOpen: true + ); + var buffer = new byte[16]; + await Assert.ThrowsAsync(() => + stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None) + ); + } + #if !LEGACY_DOTNET [Theory] [InlineData(false)]