Résumé
adm-zip: Uncontrolled memory allocation via the declared uncompressed size (DoS)
Détails de l’avis
Summary
adm-zip allocates an entry's output buffer from the declared uncompressed size (central-directory size field) before validating it against the actual data. A tiny crafted ZIP that declares a huge uncompressed size forces a multi-gigabyte allocation from a few bytes.
Impact
On adm-zip 0.5.17 (latest), Node 24, a 105-byte ZIP with one stored entry declaring size = 1,774,399,200 makes new AdmZip(buf).getEntries()[0].getData() commit ~1.8 GB of resident memory in ~4.4 s before throwing Error: ADM-ZIP: CRC32 checksum failed, roughly 16 million times the input size. Because the buffer is committed before any validation, on a memory-constrained host (containers, serverless, small VMs) the allocation OOM-kills the process before the CRC check (uncatchable), and concurrent requests can exhaust memory even on larger hosts. Any service that reads entries from untrusted ZIPs is exposed to a remote denial of service.
Steps to reproduce
Attachments are not supported in the advisory form, so the 105-byte PoC (sha256 980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386) is inlined as base64 in this self-contained reproducer:
const AdmZip = require('adm-zip');
// 105-byte crafted ZIP, base64-inlined
// sha256 980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386
const b64 = "UEsDBBQAAAAAAAAAAAAAAAAABQAAAAUAAAABAAAAYWhlbGxvUEsBAhQAFAAAAAAAAAAAAAAAAAAFAAAA4C7DaQEAAAAAAAAAAAAAAAAAAAAAAGFQSwUGAAAAAAEAAQAvAAAAJAAAAAAA";
const buf = Buffer.from(b64, "base64"); // 105 bytes
const zip = new AdmZip(buf);
zip.getEntries()[0].getData(); // commits ~1.8 GB, then throws "ADM-ZIP: CRC32 checksum failed"
The single entry declares uncompressed size = 1,774,399,200 with a compressed size of 5. getData() allocates the full declared size before the CRC check runs, so the memory is committed regardless of the (tiny) actual payload.
Root cause
zipEntry.js does Buffer.alloc(<declared uncompressed size>) before checking the declared size against the compressed size / available bytes.
Suggested fix
Validate the declared uncompressed size against the compressed size and a configurable maximum before allocating (yauzl, for example, requires the caller to bound this); reject or stream when the declared size is implausible relative to the input. Happy to send a patch.
Références
Vulnérabilités liées
Tout Supply chain →- MEDIUMCVE-2026-107387
music-metadata: Uncontrolled memory allocation in APEv2 parser
- MEDIUMCVE-2026-107388
music-metadata: ID3v2 tag size not validated before allocation, causing memory exhaustion DoS
- MEDIUMCVE-2026-107389
music-metadata: EBML parser trusts element lengths, allowing memory exhaustion or process abort
- HIGHCVE-2026-107215
Excelize: extractPart allocates attacker-controlled, unbounded and negative-sized buffers from CFB directory entries: remote panic / OOM DoS
- MEDIUMCVE-2026-105749
Docling: Unbounded table rowspan/colspan in HTML, JATS, ODS and BoxNote backends causes CPU/memory exhaustion
- MEDIUMCVE-2026-106114
ImageSharp: ICC CLUT parsing allocates from unvalidated channel and grid dimensions