Compare commits

...

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
4377f4f006 Complete fix for OutOfMemoryException in 7z extraction
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-05 09:13:07 +00:00
copilot-swe-agent[bot]
f230f24109 Refactor dictionary size validation to eliminate code duplication
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-05 09:10:28 +00:00
copilot-swe-agent[bot]
258f9b9bd8 Add dictionary size validation to prevent OutOfMemoryException in LZMA decompression
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-05 09:07:37 +00:00
copilot-swe-agent[bot]
cc503a27fb Initial plan 2026-02-05 09:00:41 +00:00
2 changed files with 46 additions and 6 deletions

View File

@@ -37,6 +37,22 @@ public class LzmaStream : Stream, IStreamStack
private readonly long _outputSize;
private readonly bool _leaveOpen;
// Maximum dictionary size to prevent OutOfMemoryException: 512MB
// This is a reasonable limit that balances memory usage with decompression capabilities.
// The LZMA spec allows up to 4GB, but such large dictionaries are rarely necessary and
// can cause OutOfMemoryException in memory-constrained environments (e.g., containers).
//
// Most 7z archives use much smaller dictionary sizes:
// - Default: 32MB for solid archives, 1MB for non-solid
// - Common maximum: 64-128MB
// - Archives requiring >512MB dictionary are extremely rare
//
// If you encounter this limit with a legitimate archive, consider:
// - Recreating the archive with a smaller dictionary size (if you control creation)
// - Increasing available memory for the application
// - Filing an issue to request a configurable limit option
private const int MaxDictionarySize = 512 * 1024 * 1024; // 512MB
private readonly int _dictionarySize;
private readonly OutWindow _outWindow = new();
private readonly RangeCoder.Decoder _rangeDecoder = new();
@@ -57,6 +73,27 @@ public class LzmaStream : Stream, IStreamStack
private readonly Encoder _encoder;
private bool _isDisposed;
private static void ValidateDictionarySize(int dictionarySize)
{
// Validate dictionary size
if (dictionarySize < 0)
{
throw new InvalidOperationException(
$"Invalid dictionary size ({dictionarySize}). The archive may be corrupted or use an unsupported format."
);
}
// Cap dictionary size to prevent OutOfMemoryException
if (dictionarySize > MaxDictionarySize)
{
throw new InvalidOperationException(
$"Dictionary size ({dictionarySize} bytes, {dictionarySize / (1024.0 * 1024.0):F2} MB) exceeds maximum allowed size ({MaxDictionarySize} bytes, {MaxDictionarySize / (1024.0 * 1024.0):F2} MB). "
+ "This archive may have been created with an extremely large dictionary size setting. "
+ "Consider recreating the archive with a smaller dictionary size."
);
}
}
public LzmaStream(byte[] properties, Stream inputStream, bool leaveOpen = false)
: this(properties, inputStream, -1, -1, null, properties.Length < 5, leaveOpen) { }
@@ -103,6 +140,8 @@ public class LzmaStream : Stream, IStreamStack
if (!isLzma2)
{
_dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1));
ValidateDictionarySize(_dictionarySize);
_outWindow.Create(_dictionarySize);
if (presetDictionary != null)
{
@@ -122,6 +161,7 @@ public class LzmaStream : Stream, IStreamStack
{
_dictionarySize = 2 | (properties[0] & 1);
_dictionarySize <<= (properties[0] >> 1) + 11;
ValidateDictionarySize(_dictionarySize);
_outWindow.Create(_dictionarySize);
if (presetDictionary != null)

View File

@@ -204,9 +204,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw=="
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -246,9 +246,9 @@
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.23, )",
"resolved": "8.0.23",
"contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q=="
"requested": "[8.0.22, )",
"resolved": "8.0.22",
"contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",