mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Merge pull request #1435 from adamhathcock/rebuild/release-06-7z-header-bounds
Reapply 7z next-header bounds validation to release
This commit is contained in:
@@ -104,6 +104,13 @@ internal sealed partial class ArchiveReader
|
||||
throw new ArchiveOperationException("nextHeaderOffset is invalid");
|
||||
}
|
||||
|
||||
// Validate the physical header range before allocating attacker-controlled sizes.
|
||||
// Subtract the validated offset instead of adding offset and size to avoid overflow.
|
||||
if (nextHeaderSize > _streamEnding - db._startPositionAfterHeader - nextHeaderOffset)
|
||||
{
|
||||
throw new IncompleteArchiveException("Next header extends beyond the end of stream.");
|
||||
}
|
||||
|
||||
_stream.Seek(nextHeaderOffset, SeekOrigin.Current);
|
||||
|
||||
var header = new byte[nextHeaderSize];
|
||||
|
||||
@@ -1043,6 +1043,13 @@ internal partial class ArchiveReader
|
||||
throw new ArchiveOperationException("nextHeaderOffset is invalid");
|
||||
}
|
||||
|
||||
// Validate the physical header range before allocating attacker-controlled sizes.
|
||||
// Subtract the validated offset instead of adding offset and size to avoid overflow.
|
||||
if (nextHeaderSize > _streamEnding - db._startPositionAfterHeader - nextHeaderOffset)
|
||||
{
|
||||
throw new IncompleteArchiveException("Next header extends beyond the end of stream.");
|
||||
}
|
||||
|
||||
_stream.Seek(nextHeaderOffset, SeekOrigin.Current);
|
||||
|
||||
var header = new byte[nextHeaderSize];
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.SevenZip;
|
||||
using SharpCompress.Crypto;
|
||||
using SharpCompress.Test.Mocks;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.SevenZip;
|
||||
|
||||
public class SevenZipHeaderValidationTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 64 * 1024 * 1024, 0)]
|
||||
[InlineData(0, 256 * 1024 * 1024, 0)]
|
||||
[InlineData(0, 2, 1)]
|
||||
[InlineData(1, 2, 2)]
|
||||
[InlineData(2, 1, 2)]
|
||||
public void SevenZipArchive_TruncatedNextHeader_ThrowsBeforeReadingHeader(
|
||||
int offset,
|
||||
int size,
|
||||
int available
|
||||
)
|
||||
{
|
||||
using var stream = CreateArchive(offset, size, available);
|
||||
using var archive = ArchiveFactory.OpenArchive(stream);
|
||||
|
||||
var exception = Assert.Throws<IncompleteArchiveException>(() => archive.Entries.ToList());
|
||||
|
||||
Assert.Equal("Next header extends beyond the end of stream.", exception.Message);
|
||||
Assert.Equal(32L, stream.Position);
|
||||
Assert.True(stream.CanRead);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 64 * 1024 * 1024, 0)]
|
||||
[InlineData(0, 256 * 1024 * 1024, 0)]
|
||||
[InlineData(0, 2, 1)]
|
||||
[InlineData(1, 2, 2)]
|
||||
[InlineData(2, 1, 2)]
|
||||
public async Task SevenZipArchive_TruncatedNextHeader_AsyncThrowsBeforeReadingHeader(
|
||||
int offset,
|
||||
int size,
|
||||
int available
|
||||
)
|
||||
{
|
||||
using var stream = CreateArchive(offset, size, available);
|
||||
using var asyncStream = new AsyncOnlyStream(stream, disposeStream: false);
|
||||
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
||||
asyncStream,
|
||||
cancellationToken: CancellationToken.None
|
||||
);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<IncompleteArchiveException>(async () =>
|
||||
{
|
||||
await foreach (
|
||||
var entry in archive.EntriesAsync.WithCancellation(CancellationToken.None)
|
||||
) { }
|
||||
});
|
||||
|
||||
Assert.Equal("Next header extends beyond the end of stream.", exception.Message);
|
||||
Assert.Equal(32L, stream.Position);
|
||||
Assert.True(stream.CanRead);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0)]
|
||||
[InlineData(0, 2, 2)]
|
||||
[InlineData(3, 2, 5)]
|
||||
public void SevenZipArchive_NextHeaderAtEnd_IsValid(int offset, int size, int available)
|
||||
{
|
||||
using var stream = CreateArchive(offset, size, available);
|
||||
using var archive = ArchiveFactory.OpenArchive(stream);
|
||||
|
||||
Assert.Empty(archive.Entries);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0)]
|
||||
[InlineData(0, 2, 2)]
|
||||
[InlineData(3, 2, 5)]
|
||||
public async Task SevenZipArchive_NextHeaderAtEnd_AsyncIsValid(
|
||||
int offset,
|
||||
int size,
|
||||
int available
|
||||
)
|
||||
{
|
||||
using var stream = CreateArchive(offset, size, available);
|
||||
using var asyncStream = new AsyncOnlyStream(stream, disposeStream: false);
|
||||
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
||||
asyncStream,
|
||||
cancellationToken: CancellationToken.None
|
||||
);
|
||||
|
||||
var count = 0;
|
||||
await foreach (var entry in archive.EntriesAsync.WithCancellation(CancellationToken.None))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
Assert.Equal(0, count);
|
||||
}
|
||||
|
||||
private static MemoryStream CreateArchive(int offset, int size, int available)
|
||||
{
|
||||
// A minimal uncompressed next header: kHeader followed by kEnd.
|
||||
byte[] header = [1, 0];
|
||||
var crc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
header
|
||||
);
|
||||
var stream = new MemoryStream();
|
||||
SevenZipSignatureHeaderWriter.WriteFinal(stream, (ulong)offset, (ulong)size, crc);
|
||||
stream.SetLength(32 + available);
|
||||
if (available >= offset + header.Length)
|
||||
{
|
||||
stream.Position = 32 + offset;
|
||||
stream.Write(header, 0, header.Length);
|
||||
}
|
||||
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user