CVE-2026-115907: Denial of Service via Excessive Memory Pre-allocation in Rust zip Crate Legacy Decoders
Vulnerability ID: CVE-2026-115907
CVSS Score: 7.5
Published: 2026-10-29
CVE-2026-115907 is an uncontrolled resource consumption vulnerability in the Rust zip crate (versions 5.1.0 through 8.6.0, and 9.0.0 pre-releases) when compiled with the optional legacy-zip feature flag. The vulnerability resides in the legacy decompression decoders—Shrink, Reduce, and Implode—which directly pass an archive-declared 64-bit uncompressed size from a ZIP64 extended information extra field to Vec::reserve() before validating the compressed data stream. By supplying a malformed ZIP archive with a declared uncompressed size exceeding isize::MAX (such as u64::MAX), an unauthenticated attacker can trigger an unhandled capacity overflow panic in the Rust standard library allocator, terminating the calling thread or aborting the entire process.
TL;DR
Applications using the Rust zip crate (>= 5.1.0, < 9.0.0) with the legacy-zip feature enabled are vulnerable to a denial-of-service condition. Passing an attacker-controlled ZIP64 uncompressed size directly to Vec::reserve() in legacy Shrink, Reduce, and Implode decoders triggers a capacity overflow panic. Upgrading to version 9.0.0 resolves the issue by clamping speculative pre-allocations to 1 MiB.
⚠️ Exploit Status: POC
Technical Details
- CVE / Advisory ID: CVE-2026-115907 / GHSA-9hj8-73fh-9963
- Weakness Class: CWE-789 (Excessive Memory Allocation) / CWE-400 (Resource Consumption)
- Attack Vector: Network (Crafted 149-byte ZIP64 Archive)
- CVSS v3.1 Score: 7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
- EPSS Score: 0.35% (28.63rd Percentile)
- Primary Impact: Denial of Service via Unhandled Capacity Overflow Panic
- Exploit Status: Proof-of-Concept Available (Regression Test)
- CISA KEV Status: Not Listed
Affected Systems
- Rust zip crate (zip-rs/zip2) with the legacy-zip feature enabled
- zip (Rust crate): >= 5.1.0, < 9.0.0 (with legacy-zip feature enabled) (Fixed in:
9.0.0)
Code Analysis
Commit: 8b49912
fix: don't pre-allocate the declared size in the legacy decoders (#991)
--- a/src/legacy/mod.rs +++ b/src/legacy/mod.rs @@ -1,4 +1,12 @@ mod huffman; + +/// Upper bound on the speculative pre-allocation made from an entry's declared +/// uncompressed size. That size comes from the archive, so it is not a fact: +/// reserving it outright panics with "capacity overflow" past `isize::MAX` and +/// aborts on allocation failure below it. Entries larger than this still decode +/// in full; the buffer just grows geometrically past the cap instead of in one step. +pub(crate) const MAX_PREALLOC: usize = 1 << 20; + pub(crate) mod implode; pub(crate) mod reduce; pub(crate) mod shrink; --- a/src/legacy/implode.rs +++ b/src/legacy/implode.rs @@ -94,7 +94,7 @@ fn hwexplode( dst: &mut Vec<u8>, ) -> std::io::Result<()> { // Pre-allocate capacity - dst.reserve(uncomp_len); + dst.reserve(uncomp_len.min(crate::legacy::MAX_PREALLOC)); let bit_length = src.len() as u64 * 8; let mut is = BitReader::endian(Cursor::new(&src), LittleEndian); @@ -211,7 +211,8 @@ impl<R: Read> Read for ImplodeDecoder<R> { self.compressed_reader.read_to_end(&mut compressed_bytes)?; // Pre-allocate stream buffer - self.stream.reserve(self.uncompressed_size as usize); + self.stream + .reserve((self.uncompressed_size as usize).min(crate::legacy::MAX_PREALLOC)); hwexplode( &compressed_bytes, --- a/src/legacy/reduce.rs +++ b/src/legacy/reduce.rs @@ -104,7 +104,7 @@ fn hwexpand(src: &[u8], uncomp_len: usize, comp_factor: u8, dst: &mut Vec<u8>) - debug_assert!((1..=4).contains(&comp_factor)); // Pre-allocate to avoid reallocations - dst.reserve(uncomp_len); + dst.reserve(uncomp_len.min(crate::legacy::MAX_PREALLOC)); let mut is = BitReader::endian(src, LittleEndian); let mut fsets = read_follower_sets(&mut is)?; --- a/src/legacy/shrink.rs +++ b/src/legacy/shrink.rs @@ -231,7 +231,7 @@ fn output_code( } fn hwunshrink(src: &[u8], uncompressed_size: usize, dst: &mut Vec<u8>) -> io::Result<()> { - dst.reserve(uncompressed_size); + dst.reserve(uncompressed_size.min(crate::legacy::MAX_PREALLOC)); let mut codetab = Codetab::create_new(); let mut queue = CodeQueue::new(); let mut is = BitReader::endian(src, LittleEndian); @@ -344,7 +344,8 @@ impl<R: Read> Read for ShrinkDecoder<R> { self.stream_read = true; let mut compressed_bytes = Vec::new(); self.compressed_reader.read_to_end(&mut compressed_bytes)?; - self.stream.reserve(self.uncompressed_size as usize); + self.stream + .reserve((self.uncompressed_size as usize).min(crate::legacy::MAX_PREALLOC)); hwunshrink( &compressed_bytes, self.uncompressed_size as usize, Exploit Details
- GitHub: Official regression test (tests/legacy_oversized_declared_size.rs) included in the fix commit demonstrating a minimal PoC generator for Shrink, Reduce, and Implode methods.
Mitigation Strategies
- Upgrade the Rust zip crate to version 9.0.0 or later to enforce the 1 MiB cap on speculative vector reservations in legacy decoders.
- Disable the optional legacy-zip Cargo feature if support for PKWARE Shrink, Reduce, and Implode algorithms is not required.
- Implement pre-extraction metadata validation using ZipArchive::by_index_raw() to reject entries declaring excessive uncompressed sizes or utilizing legacy compression methods.
- Enforce bounded reads using std::io::Read::take() when decompressing streams from untrusted archives to defend against both header manipulation and actual decompression bombs.
Remediation Steps:
- Audit project dependencies using 'cargo tree -i zip -e features' or 'cargo audit' to verify if zip (>= 5.1.0, < 9.0.0) is compiled with the 'legacy-zip' feature flag.
- Modify Cargo.toml to specify zip = ">=9.0.0" (or remove the 'legacy-zip' feature flag from existing version specifications).
- Run 'cargo update -p zip' and rebuild the application binary.
- Execute regression tests verifying that archives with ZIP64 uncompressed sizes set to u64::MAX return an io::Error rather than triggering a thread panic.
References
- GitHub Security Advisory GHSA-9hj8-73fh-9963
- GitHub Pull Request #991: fix: don't pre-allocate the declared size in the legacy decoders
- GitHub Fix Commit 8b4991288b79ad11cce201c2d785d5baa795bd3e
- GitHub Release v9.0.0
- CVE.org Record: CVE-2026-115907
- National Vulnerability Database (NVD): CVE-2026-115907
- CVEProject cvelistV5 Record for CVE-2026-115907
Read the full report for CVE-2026-115907 on our website for more details including interactive diagrams and full exploit analysis.