add zip test for blob with negative size#127990
Conversation
|
Tagging subscribers to this area: @karelz, @dotnet/area-system-io-compression |
There was a problem hiding this comment.
Pull request overview
Adds a regression/security-hardening test to the System.IO.Compression ZipArchive test suite to exercise parsing of a ZIP64 extra field that encodes an invalid (negative) uncompressed size, ensuring consumption remains safe.
Changes:
- Added a new test covering a ZIP64 extra field with
UncompressedSize = -1. - Added a byte-level ZIP builder helper to generate the malformed archive payload used by the test.
| // Attempting to actually read the data must fail safely — either by throwing, | ||
| // or by returning zero bytes (matching the actual stored data). It must NOT | ||
| // crash, hang, allocate based on the negative size, or read past the end of | ||
| // the underlying stream. Defense-in-depth in CrcValidatingReadStream rejects | ||
| // a negative expected length on Open(), so an ArgumentOutOfRangeException or | ||
| // InvalidDataException is the expected outcome here. | ||
| try | ||
| { | ||
| using Stream s = entry.Open(); | ||
| byte[] buffer = new byte[1024]; | ||
| int totalRead = 0; | ||
| int read; | ||
| while ((read = s.Read(buffer, 0, buffer.Length)) > 0) | ||
| { | ||
| totalRead += read; | ||
| // Guard against the negative size being misinterpreted as a huge unsigned | ||
| // length that would let the read loop run forever. | ||
| Assert.True(totalRead <= 1024 * 1024, "Read returned more data than the archive contains."); | ||
| } | ||
| Assert.Equal(0, totalRead); | ||
| } | ||
| catch (Exception ex) when (ex is InvalidDataException or ArgumentOutOfRangeException) | ||
| { | ||
| // Acceptable: downstream code rejects the malformed entry on Open/Read. | ||
| } |
| // Validation for IO-021: Zip64 extra field with negative UncompressedSize (-1). | ||
| // The minimal 16-byte Zip64 extra field bypasses the negative-value check in | ||
| // Zip64ExtraField.TryGetZip64BlockFromGenericExtraField (the check only runs after | ||
| // all four fields would have been read). This test verifies that the bypassed | ||
| // negative value does NOT lead to memory corruption, buffer over-read, infinite | ||
| // loop, or other harmful behavior — downstream code handles it safely. |
| long negativeUncompressedSize = -1; // 0xFFFFFFFFFFFFFFFF in two's complement | ||
| long negativeCompressedSize = 0; |
| // No file data | ||
| long dataOffset = ms.Position; | ||
|
|
||
| // Central Directory File Header |
|
@wfurt Do you want to continue with this PR? it is slowly getting stale |
- Reject negative compressed/uncompressed sizes in IsOpenableInitialVerifications so a malformed Zip64 entry surfaces as InvalidDataException instead of ArgumentOutOfRangeException. - Tighten test catch to InvalidDataException only. - Simplify test comments to describe externally observable behavior. - Rename zip64 size locals and centralDirectoryOffset for clarity.
| // The negative value propagates as-is to the entry's Length property. | ||
| // This is observable but not by itself harmful. | ||
| Assert.Equal(-1L, entry.Length); |
There was a problem hiding this comment.
I don't think this is the behavior we want, we should not return garbage data just so that we would throw later in Open(). We should validate the length ASAP in the process, which likely means during initial central directory parsing
There was a problem hiding this comment.
I agree, I believe the checks can be made inside the TryGetZip64BlockFromGenericExtraField(Async)
|
Closed this because of follow-up here: #129121 |
verifies we throw on malformed zip entry.