Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions crates/zip/RUSTSEC-0000-0000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
```toml
[advisory]
id = "RUSTSEC-0000-0000"

package = "zip"

date = "2026-10-22"

categories = ["denial-of-service"]

keywords = ["dos", "symlink", "unbounded-allocation", "extract"]

aliases = ["CVE-2026-115909", "GHSA-q7v9-x93c-xqv3"]

cvss = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"

[versions]
patched = [">= 9.0.0"]
```

# zip: `ZipArchive::extract` aborts the process on a symlink entry with a large declared size

`ZipArchive::extract()` sizes the symlink-target buffer from the uncompressed
size declared in the central directory record, before reading a single byte of
entry data:

```rust
let mut target = Vec::with_capacity(file.size() as usize);
```

Both inputs are attacker-controlled. `ZipFile::size()` returns
`uncompressed_size` verbatim from the central directory, widened to a full `u64`
by a ZIP64 extended-information extra field, and is never cross-checked against
the compressed size, the remaining archive length, or any platform-appropriate
bound for a symlink target. `ZipFile::is_symlink()` is decided entirely by the
`S_IFLNK` bit of the external-attributes field.

`Vec::with_capacity()` is infallible: when the reservation is refused it calls
`handle_alloc_error()` and aborts the process. This is not a Rust panic. It is
not caught by `catch_unwind()`, is unaffected by `panic = "unwind"`, and is not
contained by running the call on a worker thread.

A 112-byte archive is sufficient. One `Stored` entry flagged `S_IFLNK` with a
ZIP64 field declaring `uncompressed_size = 2^62` and a zero-byte payload makes
`extract()` fail with:

```
memory allocation of 4611686018427387904 bytes failed
Aborted (core dumped) # exit 134
```

The declared value only needs to exceed the host's address space, not its RAM:
`Vec::with_capacity()` maps address space without touching pages, so resident
memory stays flat and the abort happens on the refused reservation itself. Under
a `ulimit -v` cap or a memory-constrained container the threshold drops far
enough that a plain 32-bit `uncompressed_size` with no ZIP64 field suffices.

Applications that extract archives from untrusted input are affected on Unix and
Windows targets with default features and no special configuration: upload
handling, mail-attachment processing, package and artefact ingestion, backup
restore and container-image layer extraction. No confidentiality or integrity
property is affected: there is no memory unsafety, no out-of-bounds access, no
disclosure and no data corruption.

The fix landed in [zip-rs/zip2#984](https://github.com/zip-rs/zip2/pull/984) and
shipped in 9.0.0.

For further details see: https://github.com/zip-rs/zip2/security/advisories/GHSA-q7v9-x93c-xqv3
Loading