DEV Community

CVE Reports
CVE Reports

Posted on Originally published at cvereports.com

CVE-2026-115909: CVE-2026-115909: Unbounded Symlink-Target Allocation Causes Process Abort in Rust zip Crate

CVE-2026-115909: Unbounded Symlink-Target Allocation Causes Process Abort in Rust zip Crate

Vulnerability ID: CVE-2026-115909
CVSS Score: 7.5
Published: 2026-10-29

A vulnerability in the Rust zip crate (versions 1.3.0 prior to 9.0.0) allows an attacker to trigger an uncatchable process abort via a crafted ZIP archive. When extracting symbolic link entries on Unix or Windows targets, ZipArchive::extract passes the untrusted, attacker-controlled uncompressed_size header value directly to Vec::with_capacity before reading or validating entry payload data. By supplying a 112-byte archive with a ZIP64 extended information field declaring an excessive uncompressed size, an attacker forces the memory allocator to fail and invoke handle_alloc_error(), immediately terminating the entire process with SIGABRT.

TL;DR

Versions 1.3.0 to < 9.0.0 of the Rust zip crate trust the uncompressed_size field in ZIP headers when pre-allocating memory for symbolic link targets during extraction. A 112-byte malicious ZIP archive can request exabytes of memory via Vec::with_capacity, triggering an uncatchable OS-level SIGABRT that bypasses std::panic::catch_unwind and thread isolation. Upgrade to zip version 9.0.0 to enforce a 4,096-byte upper bound on symlink targets.


⚠️ Exploit Status: POC

Technical Details

  • Vulnerability Class: CWE-789 (Memory Allocation with Excessive Size Value) / CWE-400
  • Affected Component: Rust zip crate (ZipArchive::extract in src/read/mod.rs)
  • Affected Versions: >= 1.3.0, < 9.0.0
  • Patched Version: 9.0.0
  • CVSS v3.1 Score: 7.5 HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
  • EPSS Score: 0.461% (39.91th percentile)
  • Primary Impact: Uncatchable Process Abort (SIGABRT) / Denial of Service
  • Exploit Status: Public PoC Available (112-byte ZIP64 archive)

Affected Systems

  • Rust applications using the zip crate (versions >= 1.3.0, < 9.0.0) on Unix or Windows targets
  • Archive extraction services and CLI tools invoking ZipArchive::extract or ZipStreamReader on untrusted ZIP files
  • zip (Rust crate): >= 1.3.0, < 9.0.0 (Fixed in: 9.0.0)

Code Analysis

Commit: a543a1f

Reject oversized symlink targets during extraction by enforcing MAX_SYMLINK_TARGET_LEN (4096 bytes) before calling Vec::with_capacity.

File: src/read/mod.rs
@@ -15,6 +15,14 @@ use std::io::{self, Read, Seek, Write};
 use std::path::Path;
 use std::sync::OnceLock;

+/// Upper bound on the uncompressed size of a symlink entry.
+///
+/// A symlink target is a filesystem path, so its length is bounded by the
+/// platform (`PATH_MAX` is 4096 on Linux) no matter what the archive says. The
+/// uncompressed size stored in the central directory is attacker-controlled,
+/// so entries above this bound are rejected rather than pre-allocated.
+pub(crate) const MAX_SYMLINK_TARGET_LEN: u64 = 4096;
+
 mod config;
 pub use config::{ArchiveOffset, Config};

@@ -395,7 +403,22 @@ impl<R: Read + Seek> ZipArchive<R> {

             #[cfg(any(unix, windows))]
             if file.is_symlink() {
-                let mut target = Vec::with_capacity(file.size() as usize);
+                let declared_len = file.size();
+                if declared_len > MAX_SYMLINK_TARGET_LEN {
+                    return Err(invalid!(
+                        "symlink target declares {} bytes, more than the {} byte limit",
+                        declared_len,
+                        MAX_SYMLINK_TARGET_LEN
+                    ));
+                }
+
+                let mut target = Vec::with_capacity(declared_len as usize);
                 file.read_to_end(&mut target)?;
                 drop(file);
                 make_symlink(&outpath, &target, &self.shared.files)?;

File: src/read/stream.rs
@@ -78,8 +79,24 @@ impl<R: Read> ZipStreamReader<R> {
                 if file.is_symlink() {
-                    let mut target = Vec::with_capacity(file.size() as usize);
+                    let declared_len = file.size();
+                    if declared_len > MAX_SYMLINK_TARGET_LEN {
+                        return Err(invalid!(
+                            "symlink target declares {} bytes, more than the {} byte limit",
+                            declared_len,
+                            MAX_SYMLINK_TARGET_LEN
+                        ));
+                    }
+
+                    let mut target = Vec::with_capacity(declared_len as usize);
                     file.read_to_end(&mut target)?;
                     make_symlink(&outpath, &target, &self.1)?;
                     return Ok(());
Enter fullscreen mode Exit fullscreen mode

Exploit Details

  • GitHub Security Advisory: Official advisory containing the 112-byte Base64-encoded ZIP64 trigger archive and Rust reproduction harness.
  • GitHub Commit (Upstream Test Suite): Upstream patch commit including extract_symlink_with_hostile_declared_size in tests/extract_symlink.rs which programmatically constructs the exploit archive.

Mitigation Strategies

  • Upgrade the zip crate dependency to version 9.0.0 or later to enforce a 4,096-byte upper bound on symbolic link target allocations.
  • Implement a pre-extraction metadata validation loop using ZipArchive::by_index_raw to reject any archive entry where file.is_symlink() is true and file.size() > 4096 before invoking ZipArchive::extract.
  • Avoid using the high-level ZipArchive::extract convenience method on untrusted archives; instead, iterate entries manually, disable automatic symlink creation if not required by business logic, and read entry streams using std::io::Read::take to cap memory growth.
  • Isolate untrusted archive extraction workloads into dedicated child processes with strict OS resource limits (ulimit / cgroups) so that SIGABRT from handle_alloc_error cannot terminate the primary service process.

Remediation Steps:

  1. Audit Cargo.lock across all Rust repositories to identify dependencies on zip versions >= 1.3.0 and < 9.0.0 (e.g., run cargo tree -i zip).
  2. Update Cargo.toml to specify zip = "9.0.0" (or latest compatible >= 9.0.0 release) and run cargo update -p zip.
  3. If locked to an older major version (e.g., zip 7.x or 8.x), wrap all calls to ZipArchive::extract with a metadata inspection check that verifies file.size() <= 4096 whenever file.is_symlink() is true.
  4. Add regression tests using a ZIP64 symlink header with uncompressed_size = 1 << 62 to verify that the application returns an error rather than aborting.

References


Read the full report for CVE-2026-115909 on our website for more details including interactive diagrams and full exploit analysis.

Top comments (0)