Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,18 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
// 2. When the size indicates that all the information is available ("slightly invalid files").
bool readAllFields = extraField.Size >= MaximumExtraFieldLength;

// The original values are unsigned 64-bit, so a negative signed value means the
// value does not fit in Int64 and cannot be represented by the rest of the API
// (which uses long). Validate each field as it is read so that short extra fields
// (which exit early below) cannot bypass the check.

if (readUncompressedSize)
{
zip64Block._uncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(data);
if (zip64Block._uncompressedSize < 0)
{
throw new InvalidDataException(SR.FieldTooBigUncompressedSize);
}
data = data.Slice(FieldLengths.UncompressedSize);
}
else if (readAllFields)
Expand All @@ -275,6 +284,10 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (readCompressedSize)
{
zip64Block._compressedSize = BinaryPrimitives.ReadInt64LittleEndian(data);
if (zip64Block._compressedSize < 0)
{
throw new InvalidDataException(SR.FieldTooBigCompressedSize);
}
data = data.Slice(FieldLengths.CompressedSize);
}
else if (readAllFields)
Expand All @@ -290,6 +303,10 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
if (readLocalHeaderOffset)
{
zip64Block._localHeaderOffset = BinaryPrimitives.ReadInt64LittleEndian(data);
if (zip64Block._localHeaderOffset < 0)
{
throw new InvalidDataException(SR.FieldTooBigLocalHeaderOffset);
}
data = data.Slice(FieldLengths.LocalHeaderOffset);
Comment on lines 284 to 310
}
else if (readAllFields)
Expand All @@ -307,20 +324,6 @@ private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField e
zip64Block._startDiskNumber = BinaryPrimitives.ReadUInt32LittleEndian(data);
}

// original values are unsigned, so implies value is too big to fit in signed integer
if (zip64Block._uncompressedSize < 0)
{
throw new InvalidDataException(SR.FieldTooBigUncompressedSize);
}
if (zip64Block._compressedSize < 0)
{
throw new InvalidDataException(SR.FieldTooBigCompressedSize);
}
if (zip64Block._localHeaderOffset < 0)
{
throw new InvalidDataException(SR.FieldTooBigLocalHeaderOffset);
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2434,5 +2434,133 @@ public static async Task OpenWithFileAccess_DisposedArchive_Throws(bool async)
Assert.Throws<ObjectDisposedException>(() => entry.Open(FileAccess.Read));
await Assert.ThrowsAsync<ObjectDisposedException>(() => entry.OpenAsync(FileAccess.Read));
}

[Theory]
[MemberData(nameof(Get_Booleans_Data))]
public static async Task Zip64ExtraField_NegativeUncompressedSize_Throws(bool async)
{
// A ZIP64 extra field that encodes the uncompressed size as 0xFFFF_FFFF_FFFF_FFFF
// (observed as -1L) cannot be represented by the long-based public surface and is
// malformed. The central directory parser must reject it eagerly so that callers
// never see a negative Length or CompressedLength on a ZipArchiveEntry.
byte[] zipArchive = CreateZipWithNegativeZip64UncompressedSize();

await Assert.ThrowsAsync<InvalidDataException>(async () =>
{
ZipArchive archive = await CreateZipArchive(async, new MemoryStream(zipArchive), ZipArchiveMode.Read);
await DisposeZipArchive(async, archive);
});
}

private static byte[] CreateZipWithNegativeZip64UncompressedSize()
{
// Crafts a minimal ZIP whose central directory entry uses the ZIP64 sentinel
// (0xFFFFFFFF) for both compressed and uncompressed size, while the matching
// ZIP64 extra field stores -1L (0xFFFFFFFFFFFFFFFF) as the 64-bit uncompressed
// size. The local file header uses the same shape so the file is internally
// consistent up to the malformed size value.
//
// Layout:
// Local file header + ZIP64 extra (uncompressed=-1, compressed=0)
// (no file data)
// Central directory header + ZIP64 extra (uncompressed=-1, compressed=0)
// End of central directory record
using MemoryStream ms = new MemoryStream();

static void WriteUInt16(Stream stream, ushort value)
{
Span<byte> buffer = stackalloc byte[2];
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
stream.Write(buffer);
}

static void WriteUInt32(Stream stream, uint value)
{
Span<byte> buffer = stackalloc byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
stream.Write(buffer);
}

static void WriteInt64(Stream stream, long value)
{
Span<byte> buffer = stackalloc byte[8];
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
stream.Write(buffer);
}

const uint LocalFileHeaderSig = 0x04034b50;
const uint CentralDirSig = 0x02014b50;
const uint EndCentralDirSig = 0x06054b50;
const ushort VersionNeeded = 45;
const ushort VersionMadeBy = 45;
const ushort GeneralPurposeBitFlag = 0;
const ushort CompressionMethod = 0;
const ushort LastModFileTime = 0;
const ushort LastModFileDate = 0;
const uint Crc32 = 0;
const uint SentinelSize = 0xFFFFFFFF;
const ushort Zip64Tag = 1;
const ushort Zip64ExtraDataSize = 16;
const ushort Zip64ExtraTotalSize = 4 + Zip64ExtraDataSize;
const long Zip64UncompressedSize = -1;
const long Zip64CompressedSize = 0;

byte[] fileNameBytes = Encoding.UTF8.GetBytes("test.txt");

WriteUInt32(ms, LocalFileHeaderSig);
WriteUInt16(ms, VersionNeeded);
WriteUInt16(ms, GeneralPurposeBitFlag);
WriteUInt16(ms, CompressionMethod);
WriteUInt16(ms, LastModFileTime);
WriteUInt16(ms, LastModFileDate);
WriteUInt32(ms, Crc32);
WriteUInt32(ms, SentinelSize);
WriteUInt32(ms, SentinelSize);
WriteUInt16(ms, (ushort)fileNameBytes.Length);
WriteUInt16(ms, Zip64ExtraTotalSize);
ms.Write(fileNameBytes, 0, fileNameBytes.Length);
WriteUInt16(ms, Zip64Tag);
WriteUInt16(ms, Zip64ExtraDataSize);
WriteInt64(ms, Zip64UncompressedSize);
WriteInt64(ms, Zip64CompressedSize);

long centralDirectoryOffset = ms.Position;

WriteUInt32(ms, CentralDirSig);
WriteUInt16(ms, VersionMadeBy);
WriteUInt16(ms, VersionNeeded);
WriteUInt16(ms, GeneralPurposeBitFlag);
WriteUInt16(ms, CompressionMethod);
WriteUInt16(ms, LastModFileTime);
WriteUInt16(ms, LastModFileDate);
WriteUInt32(ms, Crc32);
WriteUInt32(ms, SentinelSize);
WriteUInt32(ms, SentinelSize);
WriteUInt16(ms, (ushort)fileNameBytes.Length);
WriteUInt16(ms, Zip64ExtraTotalSize);
WriteUInt16(ms, 0);
WriteUInt16(ms, 0);
WriteUInt16(ms, 0);
WriteUInt32(ms, 0);
WriteUInt32(ms, 0);
ms.Write(fileNameBytes, 0, fileNameBytes.Length);
WriteUInt16(ms, Zip64Tag);
WriteUInt16(ms, Zip64ExtraDataSize);
WriteInt64(ms, Zip64UncompressedSize);
WriteInt64(ms, Zip64CompressedSize);

long centralDirSize = ms.Position - centralDirectoryOffset;

WriteUInt32(ms, EndCentralDirSig);
WriteUInt16(ms, 0);
WriteUInt16(ms, 0);
WriteUInt16(ms, 1);
WriteUInt16(ms, 1);
WriteUInt32(ms, (uint)centralDirSize);
WriteUInt32(ms, (uint)centralDirectoryOffset);
WriteUInt16(ms, 0);

return ms.ToArray();
}
}
}
Loading