Reject a Zip64 uncompressed size that does not fit a signed int64 - #2369
Conversation
zip.File.FileInfo().Size() returns int64(UncompressedSize64), and UncompressedSize64 is read from the Zip64 extended information extra field in the central directory. A declared size of 2^63 or above turns negative on that conversion, so the running unzipSize total went negative too and stayed under UnzipSizeLimit, the UnzipXMLSizeLimit comparisons that route large parts to a temporary file were never true, and readFile reached make([]byte, 0, negative), which panics with makeslice: cap out of range. A 159 byte archive was enough. Compare the declared size against the limit as a uint64 before it is narrowed, so an entry that cannot fit is rejected on its own and the signed arithmetic below only ever sees a value within the limit. Bound the capacity hint in readFile as well, so the allocation is safe independently of how it is reached. Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2369 +/- ##
=======================================
Coverage 99.62% 99.62%
=======================================
Files 32 32
Lines 26979 26994 +15
=======================================
+ Hits 26877 26892 +15
Misses 53 53
Partials 49 49
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
xuri
left a comment
There was a problem hiding this comment.
Thanks for your contribution. I've made some changes based on your branch.
|
Thakn you @xuri Yesterday and today have been busy, will look at your feedback items later in the evening. |
No worries, I've made some changes based on your branch. Please take a look for my changes. If your have any additional suggestions, please let me know. |
Follows up on the advisory discussion, where you asked whether I wanted to send a patch.
zip.File.FileInfo().Size()returnsint64(UncompressedSize64), andUncompressedSize64comes from the Zip64 extended information extra field in the central directory. Any declared value in[2^63, 2^64)turns negative on that conversion, and the guard inReadZipReaderis signed arithmetic, so:unzipSize += fileSizemakes the running total negative, andunzipSize > f.options.UnzipSizeLimitstays false, so the decompression cap never firesUnzipXMLSizeLimitcomparisons are false as well, so a large part is never routed to a temporary filereadFilereachesmake([]byte, 0, negative), which panics withmakeslice: cap out of rangeA 159 byte archive is enough to reach it through
OpenFile,OpenReaderorReadZipReader, and nothing in the library recovers, so the panic propagates to the caller.The change
Compare the declared size against the limit as a
uint64before it is narrowed. An entry that cannot fit is rejected on its own, so the signed arithmetic below only ever sees a value already within the limit, andfileSizecan no longer be negative. TheUnzipSizeLimit < 0clause keeps the conversion honest if someone configures a negative limit.readFilealso bounds its capacity hint, so the allocation is safe regardless of how that function is reached. It takes the limit as a parameter since it is package level and has no access tof.options; there is one caller.Testing
TestZip64UncompressedSizeOverflowbuilds the archive in memory rather than committing a fixture. It asserts the declared size really is negative after conversion, then thatOpenReaderreturns the unzip size limit error instead of panicking. It also runs a control at2^63-1, one below the sign flip, which already returned the limit error before this change. That is what pins the bug to the conversion rather than to the size being large.Verified fail before and pass after: on master the new test panics with
makeslice: cap out of range, and with the fix it passes. Full suite is green locally on go1.26.5 darwin/arm64.