Repository navigation
zip_bytes_match_after_hashes inflates committed .nupkg/.jar entries with no size cap #569
Description
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.
Kind: bug. Source: §1 #1; Part 5.5; register C01.
Problem
zip_bytes_match_after_hashes pre-allocates each entry from the archive's declared size and then calls read_to_end with no bound (vendor/common.rs#L310-L341):
let mut content = Vec::with_capacity(entry.size() as usize);
if entry.read_to_end(&mut content).is_err() {The input is a committed, user-tamperable artifact (or a service archive). Only the compressed file size is capped, at 512 MiB, by read_zip_artifact (common.rs#L283-L304). The function's callers:
nuget_feed.rs#L266maven_repo.rs#L736and#L1464service_fetch.rs#L312prestage.rs#L292and#L598common.rs#L974
The same zip-entry read already exists twice in capped form, so the copies have drifted:
harvest_zip_blobs::capped(vendor/mod.rs#L528-L540) refuses a declared size overMAX_FILE_BYTES(64 MiB) and bounds the read with.take(MAX_FILE_BYTES + 1). Its comment explains why: the declared size is attacker-controlled, and the reader is bounded only by the compressed size.- An inline copy at
vendor/mod.rs#L1158-L1171repeats the same cap.
Reproduced twice on main @ 1169ae6 with an in-crate unit test (not committed). A 696 KiB zip holds one deflated entry of 700 MiB of zeros, under the matching key. Calling zip_bytes_match_after_hashes on it raises the process peak RSS (VmHWM) from 21 MiB to 726 MiB before it returns false. A committed .nupkg well under the 512 MiB file cap can therefore inflate ~1000× during a vendor or scan re-run.
Symptoms
None filed.
Impact
A committed artifact can exhaust memory (DoS) on a CI runner during vendored NuGet/Maven re-runs and service-archive verification. The fix is small.
Proposed change
- Hoist one capped zip-entry reader into
vendor/common.rs, for exampleread_zip_entry_capped(entry, cap) -> Option<Vec<u8>>. It refuses a declared size over the cap, reads through.take(cap + 1), and rejects any overflow. - Use it in
zip_bytes_match_after_hashesand in bothvendor/mod.rssites. Delete the nestedcappedfn and the inline copy. - Use one per-entry cap constant (
MAX_FILE_BYTES, 64 MiB). An over-cap entry returnsfalse(out of sync), which is the fail-safe answer.
Size and scope
vendor/common.rs and vendor/mod.rs, ~40 production lines plus tests. Out of scope: unifying the 512/256/128 MiB archive caps (C01's second half, part of C15/C21).
Acceptance criteria
- One capped zip-entry reader, used by all three sites. No
Vec::with_capacity(entry.size()…)without a cap check remains invendor/. - Regression test: a small zip whose entry inflates past the cap makes
zip_bytes_match_after_hashesreturnfalsewithout allocating past the cap. Another test covers a zip whose entry declares a size over the cap. - The existing NuGet/Maven vendored, prestage and service-fetch tests stay green.
Dependencies
None.
Activity
mikolalysenko commentedon Oct 2, 2026
[agent] Triaged: priority:p3 (NuGet/Maven vendored artifacts, shared zip helper). No duplicate or fix PR found.
Generated by Claude Code
mikolalysenko commentedon Oct 2, 2026
We can assume this data is trusted to not be too big, but for performance this should actually be streaming.
mikolalysenko commentedon Oct 2, 2026
[agent] Re-triaged after the maintainer note: scope is now streaming the
.nupkg/.jarentry comparison instead of inflating whole entries into memory, not adding a size cap. Stayspriority:p3. Not clustered with #571 (different code path).Generated by Claude Code