Preserve legacy 7z property size compatibility during preflight

This commit is contained in:
Adam Hathcock
2026-11-13 09:36:55 +00:00
parent 101c939cc6
commit 2f040283fc
3 changed files with 119 additions and 3 deletions

View File

@@ -338,7 +338,11 @@ internal sealed partial class ArchiveReader
}
var numFiles = ReadNum();
ValidateFileCount(numFiles, unpackSizes.Count);
ValidateFileCount(
numFiles,
unpackSizes.Count,
db._majorVersion > 0 || db._minorVersion > 2
);
db._files = new List<CFileItem>(numFiles);
for (var i = 0; i < numFiles; i++)
{

View File

@@ -714,8 +714,18 @@ internal partial class ArchiveReader
return (int)size;
}
private void ValidateFileCount(int numFiles, int numStreams)
private void ValidateFileCount(int numFiles, int numStreams, bool checkRecordsSize)
{
if (!checkRecordsSize)
{
// Before 0.3, known properties may have inaccurate sizes, so walking
// them by their declared lengths would disagree with the real parser.
// Bound the allocation by the minimum possible empty-stream bitmap;
// ReadBitVector separately bounds the actual bitmap at its read position.
var minimumEmptyStreams = Math.Max(0L, (long)numFiles - numStreams);
_currentReader.RequireBytes((minimumEmptyStreams + 7) / 8);
return;
}
// Nonempty files correspond to already parsed substreams. Empty files
// require one bit each in kEmptyStream, not one byte per file. Preflight
// that property before allocating CFileItem objects or implicit vectors.
@@ -914,7 +924,11 @@ internal partial class ArchiveReader
}
var numFiles = ReadNum();
ValidateFileCount(numFiles, unpackSizes.Count);
ValidateFileCount(
numFiles,
unpackSizes.Count,
db._majorVersion > 0 || db._minorVersion > 2
);
db._files = new List<CFileItem>(numFiles);
for (var i = 0; i < numFiles; i++)
{

View File

@@ -356,6 +356,104 @@ public class SevenZipHeaderAllocationTests
Assert.True(stream.CanRead);
}
[Theory]
[InlineData(2, 0, false)]
[InlineData(2, 0, true)]
[InlineData(2, 127, false)]
[InlineData(2, 127, true)]
[InlineData(3, 0, false)]
[InlineData(3, 0, true)]
public async Task SevenZipArchive_Property_Size_Validation_Is_Version_Aware(
byte minorVersion,
byte declaredSize,
bool useAsync
)
{
// Both known properties have deliberately inaccurate sizes, including
// a name preceding the empty-stream bitmap.
using var stream = CreateArchive([
1,
5,
1,
17,
declaredSize,
0,
(byte)'x',
0,
0,
0,
14,
declaredSize,
0x80,
0,
0,
]);
stream.GetBuffer()[7] = minorVersion;
if (useAsync)
{
using var asyncStream = new AsyncOnlyStream(stream, disposeStream: false);
await using var archive = await ArchiveFactory.OpenAsyncArchive(
asyncStream,
cancellationToken: CancellationToken.None
);
if (minorVersion > 2)
{
await Assert.ThrowsAnyAsync<SharpCompressException>(async () =>
await archive.EntriesAsync.ToListAsync(CancellationToken.None)
);
}
else
{
var entry = Assert.Single(
await archive.EntriesAsync.ToListAsync(CancellationToken.None)
);
Assert.True(entry.IsDirectory);
Assert.Equal("x", entry.Key);
}
}
else
{
using var archive = ArchiveFactory.OpenArchive(stream);
if (minorVersion > 2)
{
Assert.ThrowsAny<SharpCompressException>(() => archive.Entries.ToList());
}
else
{
var entry = Assert.Single(archive.Entries);
Assert.True(entry.IsDirectory);
Assert.Equal("x", entry.Key);
}
}
Assert.True(stream.CanRead);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task SevenZipArchive_Legacy_File_Count_Is_Bounded_By_Available_Metadata(
bool useAsync
)
{
using var stream = CreateArchive(Join([1, 5], Number(100_000), [14, 0, 0x80, 0]));
stream.GetBuffer()[7] = 2;
if (useAsync)
{
await using var archive = await ArchiveFactory.OpenAsyncArchive(
stream,
cancellationToken: CancellationToken.None
);
await Assert.ThrowsAsync<IncompleteArchiveException>(async () =>
await archive.EntriesAsync.ToListAsync(CancellationToken.None)
);
}
else
{
using var archive = ArchiveFactory.OpenArchive(stream);
Assert.Throws<IncompleteArchiveException>(() => archive.Entries.ToList());
}
}
private static byte[] MetadataHeader(
bool encoded,
ulong unpackSize,