Merge pull request #1473 from adamhathcock/review/fix-zip64-descriptor-width

Fix ZIP64 streaming data descriptor width parsing
This commit is contained in:
Adam Hathcock
2026-10-26 11:36:16 +00:00
committed by GitHub
5 changed files with 408 additions and 58 deletions

View File

@@ -56,6 +56,15 @@ internal partial class LocalEntryHeader : ZipFileEntry
}
var zip64ExtraData = Extra.OfType<Zip64ExtendedInformationExtraField>().FirstOrDefault();
// Remember ZIP64 before the extra field replaces size sentinels with values
// that may be small (or zero placeholders). Descriptor width must not depend
// on those values, nor on Version, which other compression methods also raise.
UsesZip64Sizes =
CompressedSize == uint.MaxValue
|| UncompressedSize == uint.MaxValue
|| zip64ExtraData is not null;
if (zip64ExtraData != null)
{
zip64ExtraData.Process(UncompressedSize, CompressedSize, 0, 0);
@@ -102,4 +111,10 @@ internal partial class LocalEntryHeader : ZipFileEntry
}
internal ushort Version { get; private set; }
/// <summary>
/// Whether the local header declares ZIP64 sizes, requiring 8-byte descriptor sizes
/// even for small entries (APPNOTE 4.3.9.2).
/// </summary>
internal bool UsesZip64Sizes { get; private set; }
}

View File

@@ -128,44 +128,57 @@ internal sealed partial class StreamingZipHeaderFactory
lastEntryHeader.Crc = crc;
lastEntryHeader.IsCrcAvailable = true;
//attempt 32bit read
ulong compressedSize = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
ulong uncompressedSize = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
headerBytes = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
ulong compressedSize;
ulong uncompressedSize;
//check for zip64 sentinel or unexpected header
bool isSentinel =
compressedSize == 0xFFFFFFFF || uncompressedSize == 0xFFFFFFFF;
bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50;
if (!isHeader && !isSentinel)
// A ZIP64 size's low word can equal a header signature. Honor the
// declared width rather than mistaking that word for the next header,
// even if the record following the descriptor is unrecognized.
if (lastEntryHeader.UsesZip64Sizes)
{
//reshuffle into 64-bit values
compressedSize = (uncompressedSize << 32) | compressedSize;
// headerBytes holds the low word; the high word follows on the wire.
var highUncompressedSize = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
uncompressedSize = headerBytes | ((ulong)highUncompressedSize << 32);
headerBytes = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
}
else if (isSentinel)
{
//standards-compliant zip64 descriptor
compressedSize = await _reader
.ReadUInt64Async(_cancellationToken)
.ConfigureAwait(false);
uncompressedSize = await _reader
.ReadUInt64Async(_cancellationToken)
.ConfigureAwait(false);
headerBytes = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
}
else
{
// Without ZIP64 metadata, retain compatibility with older writers
// that emitted 64-bit descriptors without declaring them locally.
compressedSize = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
uncompressedSize = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
headerBytes = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
// Keep the legacy probe limited to local/central headers: other
// record signatures can be the low word of a 64-bit size.
if (
headerBytes != ENTRY_HEADER_BYTES
&& headerBytes != DIRECTORY_START_HEADER_BYTES
)
{
// Descriptor words are size data, including 0xFFFFFFFF; unlike
// local headers, descriptors do not contain size sentinels.
compressedSize = (uncompressedSize << 32) | compressedSize;
// headerBytes holds the low word; the high word follows on the wire.
var highUncompressedSize = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
uncompressedSize = headerBytes | ((ulong)highUncompressedSize << 32);
headerBytes = await _reader
.ReadUInt32Async(_cancellationToken)
.ConfigureAwait(false);
}
}
lastEntryHeader.CompressedSize = (long)compressedSize;

View File

@@ -68,28 +68,40 @@ internal sealed partial class StreamingZipHeaderFactory : ZipHeaderFactory
_lastEntryHeader.Crc = crc;
_lastEntryHeader.IsCrcAvailable = true;
//attempt 32bit read
ulong compSize = reader.ReadUInt32();
ulong uncompSize = reader.ReadUInt32();
headerBytes = reader.ReadUInt32();
ulong compSize;
ulong uncompSize;
//check for zip64 sentinel or unexpected header
bool isSentinel = compSize == 0xFFFFFFFF || uncompSize == 0xFFFFFFFF;
bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50;
if (!isHeader && !isSentinel)
// A ZIP64 size's low word can equal a header signature. Honor the
// declared width rather than mistaking that word for the next header,
// even if the record following the descriptor is unrecognized.
if (_lastEntryHeader.UsesZip64Sizes)
{
//reshuffle into 64-bit values
compSize = (uncompSize << 32) | compSize;
// headerBytes holds the low word; the high word follows on the wire.
uncompSize = headerBytes | ((ulong)reader.ReadUInt32() << 32);
headerBytes = reader.ReadUInt32();
}
else if (isSentinel)
{
//standards-compliant zip64 descriptor
compSize = reader.ReadUInt64();
uncompSize = reader.ReadUInt64();
headerBytes = reader.ReadUInt32();
}
else
{
// Without ZIP64 metadata, retain compatibility with older writers
// that emitted 64-bit descriptors without declaring them locally.
compSize = reader.ReadUInt32();
uncompSize = reader.ReadUInt32();
headerBytes = reader.ReadUInt32();
// Keep the legacy probe limited to local/central headers: other
// record signatures can be the low word of a 64-bit size.
if (
headerBytes != ENTRY_HEADER_BYTES
&& headerBytes != DIRECTORY_START_HEADER_BYTES
)
{
// Descriptor words are size data, including 0xFFFFFFFF; unlike
// local headers, descriptors do not contain size sentinels.
compSize = (uncompSize << 32) | compSize;
// headerBytes holds the low word; the high word follows on the wire.
uncompSize = headerBytes | ((ulong)reader.ReadUInt32() << 32);
headerBytes = reader.ReadUInt32();
}
}
_lastEntryHeader.CompressedSize = (long)compSize;