Report invalid LZMA2 chunk properties as a data error

An LZMA2 chunk header properties byte of 225 or more cannot occur in
valid data, but it reached Decoder.SetDecoderProperties and surfaced as
InvalidParamException. EncryptedDecoderStream only maps DataErrorException
to CryptographicException, so a wrong 7z password occasionally leaked the
internal codec exception, making
SevenZipWriter_Encrypted_WrongPasswordFails flaky.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Adam Hathcock
2026-10-27 15:02:07 +00:00
parent 51c00c62d8
commit c4028139fd
3 changed files with 45 additions and 9 deletions

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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<DataErrorException>(() => 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<DataErrorException>(() =>
stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None)
);
}
#if !LEGACY_DOTNET
[Theory]
[InlineData(false)]