Validate uncompressed size up front in ZipArchiveEntry update mode#128319
Validate uncompressed size up front in ZipArchiveEntry update mode#128319Copilot wants to merge 6 commits into
Conversation
…127834) Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d2fb03b5-3b19-489d-9856-bb2c42a4d7e4 Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com>
|
Tagging subscribers to this area: @karelz, @dotnet/area-system-io-compression |
|
Hey @copilot, I think the fix should be different. We shouldn’t rely on the MemoryStream hitting its max capacity to fail. Instead, we should validate the uncompressedSize up front in ZipArchiveEntry and ensure it falls within the valid range (0 to Array.MaxLength). If it doesn’t, we should throw immediately when opening the entry in update mode. |
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0fbb8331-f2e4-4bd0-acb1-a58139224285 Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com>
Switched to up-front validation in ff62bbb. |
There was a problem hiding this comment.
Pull request overview
This PR tightens validation for ZipArchiveEntry when opening entries in Update mode by rejecting entries whose declared uncompressed size can’t be represented by the in-memory MemoryStream buffer used for Update-mode editing, and adds a regression test to cover the exception behavior for both sync and async open paths.
Changes:
- Add an Update-mode openability check that fails early when
_uncompressedSizeexceedsArray.MaxLength, returningInvalidDataExceptioninstead of allowing an(int)cast to wrap and triggerMemoryStream(int)ArgumentOutOfRangeException. - Update comments in sync/async
GetUncompressedData*to reflect the new upstream validation. - Add a regression test that forces
_uncompressedSize > Array.MaxLengthand assertsOpen()/OpenAsync()throwInvalidDataException.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs | Adds upfront Update-mode validation for _uncompressedSize and updates the GetUncompressedData comment accordingly. |
| src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.Async.cs | Updates the async GetUncompressedDataAsync comment to match the new validation behavior. |
| src/libraries/System.IO.Compression/tests/ZipArchive/zip_InvalidParametersAndStrangeFiles.cs | Adds a regression test ensuring Update-mode Open/OpenAsync throws InvalidDataException for oversized declared uncompressed sizes. |
|
@copilot fix these comments |
…ss gating Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com>
Fixed in the latest commit. Two changes made:
|
|
@copilot I liked this option better: move the size check to GetUncompressedData/GetUncompressedDataAsync right before constructing the MemoryStream) |
…emoryStream construction Co-authored-by: alinpahontu2912 <56953855+alinpahontu2912@users.noreply.github.com>
Done in the latest commit. The size check is now in |
| <data name="EntryTooLarge" xml:space="preserve"> | ||
| <value>Entries larger than 4GB are not supported in Update mode.</value> | ||
| </data> | ||
| <data name="EntryUncompressedSizeTooLargeForUpdateMode" xml:space="preserve"> |
There was a problem hiding this comment.
@rzikm since a new error message was added, does it count as breaking change?
There was a problem hiding this comment.
I don't think we consider change of exception message a breaking change. We only consider it breaking if we change the exception type to something the method never has thrown before (and wasn't documented as a possibility)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
ZipArchiveEntry.OpenthrowsArgumentOutOfRangeExceptionfrom theMemoryStreamconstructor whenever an entry's declared_uncompressedSizeexceedsArray.MaxLength: the unchecked(int)cast inGetUncompressedDatawraps negative, andMemoryStream(int capacity)validatescapacityis in[0, Array.MaxLength]. The accompanying comment claimed this was safe ("MemoryStreamwill just grow") — true on .NET Framework, no longer true on .NET Core.Description
ZipArchiveEntry.GetUncompressedDataandGetUncompressedDataAsync: both methods now validate_uncompressedSize <= Array.MaxLengthdirectly before constructing theMemoryStream. If the check fails, anInvalidDataException(SR.EntryUncompressedSizeTooLargeForUpdateMode)is thrown immediately — right at the point where the size limit is actually enforced — rather than letting an unchecked(int)cast wrap to negative and surface as a misleadingArgumentOutOfRangeExceptionfrom theMemoryStreamconstructor.IsOpenableFinalVerifications: theneedToUncompressparameter has been removed. The uncompressed-size check is no longer placed here; only the compressed-size checks remain. This means archive construction (ThrowIfNotOpenable(needToUncompress: false, needToLoadIntoMemory: true)) is entirely unaffected — opening an update-mode archive never fails for entries whose large declared size may never be opened.SR.EntryUncompressedSizeTooLargeForUpdateMode: "Entries with uncompressed data larger than 2GB are not supported in Update mode." — accurately reflects theArray.MaxLength(~2 GB) ceiling enforced byMemoryStream, replacing the previous reuse ofSR.EntryTooLarge("Entries larger than 4GB...") which was misleading for entries between ~2 GB and 4 GB.zip_InvalidParametersAndStrangeFiles.cs: reflectively forces_uncompressedSize > Array.MaxLengthand asserts that bothOpenandOpenAsyncthrowInvalidDataExceptionimmediately in update mode (rather than the previouscapacity '-2147483549'ArgumentOutOfRangeExceptionfromMemoryStream).Scope / non-goals
Entries whose declared uncompressed size exceeds
Array.MaxLengthcannot be loaded in update mode —MemoryStreamis backed by a singlebyte[]and cannot grow beyondArray.MaxLength. Such entries are now rejected whenOpen()/OpenAsync()is called with a descriptiveInvalidDataException("Entries with uncompressed data larger than 2GB are not supported in Update mode.")instead of surfacing as a misleadingArgumentOutOfRangeExceptionabout a negativecapacityargument. Opening the archive itself is unaffected — entries with large declared sizes that are never opened do not cause failures. Lifting the underlying ceiling requires replacing the in-memory update buffer with a segmented or file-backed stream and is out of scope.Note
This pull request was authored by GitHub Copilot.