Skip to content
Draft
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 @@ -115,8 +115,9 @@ private async Task<MemoryStream> GetUncompressedDataAsync(CancellationToken canc
{
// this means we have never opened it before

// if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
// grow as data is copied into it
// OpenInUpdateModeAsync validates that _uncompressedSize fits in [0, Array.MaxLength]
// via ThrowIfNotOpenableAsync before reaching this code, so the (int) cast is safe
// and the capacity hint is bounded by MemoryStream's maximum.
_storedUncompressedData = new MemoryStream((int)_uncompressedSize);

if (_originallyInArchive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,9 @@ private MemoryStream GetUncompressedData()
{
// this means we have never opened it before

// if _uncompressedSize > int.MaxValue, it's still okay, because MemoryStream will just
// grow as data is copied into it
// OpenInUpdateMode validates that _uncompressedSize fits in [0, Array.MaxLength]
// via ThrowIfNotOpenable before reaching this code, so the (int) cast is safe
// and the capacity hint is bounded by MemoryStream's maximum.
_storedUncompressedData = new MemoryStream((int)_uncompressedSize);

if (_originallyInArchive)
Expand Down Expand Up @@ -1000,6 +1001,18 @@ private bool IsOpenableFinalVerifications(bool needToLoadIntoMemory, long offset
return false;
}
}

// The uncompressed data is loaded into a MemoryStream, which is backed by a
// single byte[] and therefore cannot grow beyond Array.MaxLength. Reject
// up front rather than failing later from the MemoryStream constructor with
// a misleading argument-out-of-range exception (caused by the unchecked
// (int) cast in GetUncompressedData wrapping a long > int.MaxValue to a
// negative value).
if ((ulong)_uncompressedSize > (ulong)Array.MaxLength)
{
message = SR.EntryTooLarge;
return false;
}
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,46 @@ await Assert.ThrowsAsync<InvalidDataException>(async () =>
await DisposeZipArchive(async, archive);
}

[Theory]
[MemberData(nameof(Get_Booleans_Data))]
public static async Task ZipArchiveEntry_OpenInUpdateMode_UncompressedSizeGreaterThanArrayMaxLength_ThrowsInvalidData(bool async)
{
// When _uncompressedSize > Array.MaxLength, the entry's uncompressed payload
// cannot be loaded into a MemoryStream (which is backed by a single byte[] and
// therefore bounded by Array.MaxLength). The entry must be rejected up front
// with a descriptive InvalidDataException when opened in Update mode, rather
// than failing later from the MemoryStream constructor with a misleading
// argument-out-of-range exception caused by the (int) cast wrapping negative.
byte[] payload = [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF];
MemoryStream stream = new MemoryStream();

ZipArchive archive = await CreateZipArchive(async, stream, ZipArchiveMode.Create, leaveOpen: true);
ZipArchiveEntry entry = archive.CreateEntry("entry.bin", CompressionLevel.NoCompression);
Stream entryStream = await OpenEntryStream(async, entry);
await entryStream.WriteAsync(payload);
await DisposeStream(async, entryStream);
await DisposeZipArchive(async, archive);

stream.Position = 0;
archive = await CreateZipArchive(async, stream, ZipArchiveMode.Update, leaveOpen: true);
entry = archive.GetEntry("entry.bin");

FieldInfo uncompressedSizeField = typeof(ZipArchiveEntry).GetField("_uncompressedSize", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(uncompressedSizeField);
uncompressedSizeField.SetValue(entry, (long)Array.MaxLength + 1L);

if (async)
{
await Assert.ThrowsAsync<InvalidDataException>(() => entry.OpenAsync());
}
else
{
Assert.Throws<InvalidDataException>(() => entry.Open());
}

await DisposeZipArchive(async, archive);
}

[Theory]
[MemberData(nameof(Get_Booleans_Data))]
public static async Task UnseekableVeryLargeArchive_DataDescriptor_Read_Zip64(bool async)
Expand Down
Loading