fix: cap each extracted archive entry at 2 GB

`put` had no per-entry cap, unlike the legacy java path which capped every file at 2 GB through `intFile.SafeCopy`. At depth 1, one entry of an overlapping-entry zip bomb could decompress all the way to the 100 GB disk limit before anything stopped it.

- entries are now read through the same 2 GB cap (`intFile.PerFileReadLimit`, now exported). An entry over it is dropped after cap+1 bytes and the rest of the archive is still extracted
- `Resolver` gains `TruncatedReason`, so the truncation unknown says what happened ("an entry larger than 2.0 GiB was skipped", or the existing disk-limit text) instead of always blaming the limits

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2026-09-24 13:21:08 -04:00
parent 27655c2be0
commit d27b6ebb1b
5 changed files with 67 additions and 9 deletions
+4 -3
View File
@@ -6,12 +6,13 @@ import (
"io"
)
const perFileReadLimit = 2 * GB
// PerFileReadLimit is the most read from any one archive entry, guarding against decompression bombs.
const PerFileReadLimit = 2 * GB
// SafeCopy bounds the copy to guard against decompression bombs when extracting from archives.
func SafeCopy(writer io.Writer, reader io.Reader) error {
numBytes, err := io.Copy(writer, io.LimitReader(reader, perFileReadLimit))
if numBytes >= perFileReadLimit {
numBytes, err := io.Copy(writer, io.LimitReader(reader, PerFileReadLimit))
if numBytes >= PerFileReadLimit {
return fmt.Errorf("zip read limit hit (potential decompression bomb attack)")
}
// Propagate decompression / read errors up to the caller. io.Copy