Reject truncated LZMA2 headers and prevent stalled decoding

This commit is contained in:
Adam Hathcock
2026-10-15 10:44:50 +01:00
parent e4193af83b
commit bc1ade2bbc
3 changed files with 201 additions and 10 deletions

View File

@@ -201,6 +201,11 @@ public partial class LzmaStream
}
}
if (_availableBytes < 0)
{
throw new DataErrorException();
}
var toProcess = count - total;
if (toProcess > _availableBytes)
{
@@ -224,6 +229,10 @@ public partial class LzmaStream
}
var read = _outWindow.Read(buffer, offset, toProcess);
if (read == 0 && _availableBytes > 0)
{
throw new DataErrorException();
}
total += read;
offset += read;
_position += read;
@@ -317,6 +326,11 @@ public partial class LzmaStream
}
}
if (_availableBytes < 0)
{
throw new DataErrorException();
}
var toProcess = count - total;
if (toProcess > _availableBytes)
{
@@ -340,6 +354,10 @@ public partial class LzmaStream
}
var read = _outWindow.Read(buffer, offset, toProcess);
if (read == 0 && _availableBytes > 0)
{
throw new DataErrorException();
}
total += read;
offset += read;
_position += read;

View File

@@ -266,6 +266,11 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
}
}
if (_availableBytes < 0)
{
throw new DataErrorException();
}
var toProcess = count - total;
if (toProcess > _availableBytes)
{
@@ -283,6 +288,11 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
}
var read = _outWindow.Read(buffer, offset, toProcess);
// Unknown-size raw LZMA can end without output; otherwise no progress is corruption.
if (read == 0 && _availableBytes > 0)
{
throw new DataErrorException();
}
total += read;
offset += read;
_position += read;
@@ -416,10 +426,21 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
return value;
}
private int ReadChunkHeaderByte()
{
var value = _inputStream!.ReadByte();
// EOF is not an LZMA2 end marker and must never enter chunk-size arithmetic.
if (value < 0)
{
throw new DataErrorException();
}
_inputPosition++;
return value;
}
private void DecodeChunkHeader()
{
var control = _inputStream!.ReadByte();
_inputPosition++;
var control = ReadChunkHeaderByte();
if (control == 0x00)
{
@@ -448,17 +469,14 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
_uncompressedChunk = false;
_availableBytes = (control & 0x1F) << 16;
_availableBytes += (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1;
_inputPosition += 2;
_availableBytes += (ReadChunkHeaderByte() << 8) + ReadChunkHeaderByte() + 1;
_rangeDecoderLimit = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1;
_inputPosition += 2;
_rangeDecoderLimit = (ReadChunkHeaderByte() << 8) + ReadChunkHeaderByte() + 1;
if (control >= 0xC0)
{
_needProps = false;
Properties[0] = (byte)_inputStream.ReadByte();
_inputPosition++;
Properties[0] = (byte)ReadChunkHeaderByte();
_decoder = new Decoder();
_decoder.SetDecoderProperties(Properties);
@@ -482,8 +500,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
else
{
_uncompressedChunk = true;
_availableBytes = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1;
_inputPosition += 2;
_availableBytes = (ReadChunkHeaderByte() << 8) + ReadChunkHeaderByte() + 1;
}
}