Search
Find a vulnerability
Search criteria
2 vulnerabilities by silviucpp
CVE-2026-119434 (GCVE-0-2026-119434)
Vulnerability from nvd – Published: 2026-11-05 20:15 – Updated: 2026-11-05 20:45
VLAI
EPSS
VEX
Title
Decompression bomb in ezstd:decompress_using_ddict/2 allows BEAM memory exhaustion via unbounded output buffer
Summary
Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to ezstd:decompress_using_ddict/2 to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.
zstd_nif_decompress_using_ddict in c_src/ezstd_nif.cc reads the frame's declared content size with ZSTD_getFrameContentSize and passes it directly to enif_make_new_binary, without the 1 GiB MAX_BUFFER_SIZE check that ezstd:decompress/1 applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by ezstd:decompress/1 but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.
This issue affects ezstd: from 1.0.5 before 1.2.5.
Severity
6.2 (Medium)
SSVC
Exploitation: none
Automatable: no
Technical Impact: partial
CISA Coordinator · CISA-ADP (v2.0.3)
Decision recorded 2026-11-05 20:45 UTC
CWE
- CWE-409 - Improper Handling of Highly Compressed Data (Data Amplification)
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/silviucpp/ezstd/releases/tag/v1.2.5 | release-notesrelated |
| https://cna.erlef.org/cves/CVE-2026-119434.html | relatedthird-party-advisory |
| https://osv.dev/vulnerability/EEF-CVE-2026-119434 | related |
| https://github.com/silviucpp/ezstd/commit/6730128… | related |
| https://github.com/silviucpp/ezstd/commit/e34f4a4… | patch |
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"cvssV3_1": {
"attackComplexity": "LOW",
"attackVector": "LOCAL",
"availabilityImpact": "HIGH",
"baseScore": 6.2,
"baseSeverity": "MEDIUM",
"confidentialityImpact": "NONE",
"integrityImpact": "NONE",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"version": "3.1"
}
},
{
"other": {
"content": {
"id": "CVE-2026-119434",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-11-05T20:45:27.204341Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-11-05T20:45:33.490Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:silviucpp:ezstd:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"ezstd_nif",
"ezstd"
],
"packageName": "ezstd",
"packageURL": "pkg:hex/ezstd",
"product": "ezstd",
"programFiles": [
"c_src/ezstd_nif.cc",
"src/ezstd_nif.erl",
"src/ezstd.erl"
],
"programRoutines": [
{
"name": "zstd_nif_decompress_using_ddict"
},
{
"name": "ezstd_nif:decompress_using_ddict/2"
},
{
"name": "ezstd:decompress_using_ddict/2"
}
],
"repo": "https://github.com/silviucpp/ezstd",
"vendor": "silviucpp",
"versions": [
{
"lessThan": "1.2.5",
"status": "affected",
"version": "1.0.5",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:silviucpp:ezstd:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"ezstd_nif",
"ezstd"
],
"packageName": "silviucpp/ezstd",
"packageURL": "pkg:github/silviucpp/ezstd",
"product": "ezstd",
"programFiles": [
"c_src/ezstd_nif.cc",
"src/ezstd_nif.erl",
"src/ezstd.erl"
],
"programRoutines": [
{
"name": "zstd_nif_decompress_using_ddict"
},
{
"name": "ezstd_nif:decompress_using_ddict/2"
},
{
"name": "ezstd:decompress_using_ddict/2"
}
],
"repo": "https://github.com/silviucpp/ezstd",
"vendor": "silviucpp",
"versions": [
{
"lessThan": "e34f4a4f6dc18d990d1cece2d5fceef8e934a195",
"status": "affected",
"version": "6730128b1d35eca02a2028671421471ff651f423",
"versionType": "git"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:silviucpp:ezstd:*:*:*:*:*:*:*:*",
"versionEndExcluding": "1.2.5",
"versionStartIncluding": "1.0.5",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Oz Kaiser"
},
{
"lang": "en",
"type": "reporter",
"value": "Oz Kaiser"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Silviu Caragea"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-11-04T17:02:50.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to \u003ccode\u003eezstd:decompress_using_ddict/2\u003c/code\u003e to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ezstd_nif_decompress_using_ddict\u003c/code\u003e in \u003ccode\u003ec_src/ezstd_nif.cc\u003c/code\u003e reads the frame\u0027s declared content size with \u003ccode\u003eZSTD_getFrameContentSize\u003c/code\u003e and passes it directly to \u003ccode\u003eenif_make_new_binary\u003c/code\u003e, without the 1 GiB \u003ccode\u003eMAX_BUFFER_SIZE\u003c/code\u003e check that \u003ccode\u003eezstd:decompress/1\u003c/code\u003e applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by \u003ccode\u003eezstd:decompress/1\u003c/code\u003e but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.\u003c/p\u003e\n\u003cp\u003eThis issue affects ezstd: from 1.0.5 before 1.2.5.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to `ezstd:decompress_using_ddict/2` to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.\n\n`zstd_nif_decompress_using_ddict` in `c_src/ezstd_nif.cc` reads the frame\u0027s declared content size with `ZSTD_getFrameContentSize` and passes it directly to `enif_make_new_binary`, without the 1 GiB `MAX_BUFFER_SIZE` check that `ezstd:decompress/1` applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by `ezstd:decompress/1` but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.\n\nThis issue affects ezstd: from 1.0.5 before 1.2.5."
}
],
"value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to ezstd:decompress_using_ddict/2 to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.\n\nzstd_nif_decompress_using_ddict in c_src/ezstd_nif.cc reads the frame\u0027s declared content size with ZSTD_getFrameContentSize and passes it directly to enif_make_new_binary, without the 1 GiB MAX_BUFFER_SIZE check that ezstd:decompress/1 applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by ezstd:decompress/1 but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.\n\nThis issue affects ezstd: from 1.0.5 before 1.2.5."
}
],
"impacts": [
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker who can submit a zstd frame to a service that decompresses it with a fixed dictionary can crash the entire Erlang VM with a single request of about 65 KiB. Every application and process hosted in that VM is taken down, not only the one that called the decompressor.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "An attacker who can submit a zstd frame to a service that decompresses it with a fixed dictionary can crash the entire Erlang VM with a single request of about 65 KiB. Every application and process hosted in that VM is taken down, not only the one that called the decompressor."
}
],
"value": "An attacker who can submit a zstd frame to a service that decompresses it with a fixed dictionary can crash the entire Erlang VM with a single request of about 65 KiB. Every application and process hosted in that VM is taken down, not only the one that called the decompressor."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "LOCAL",
"baseScore": 6.9,
"baseSeverity": "MEDIUM",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-409",
"description": "CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-11-05T20:15:56.311Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "ezstd v1.2.5 release notes",
"tags": [
"release-notes",
"related"
],
"url": "https://github.com/silviucpp/ezstd/releases/tag/v1.2.5"
},
{
"name": "EEF CNA record for CVE-2026-119434",
"tags": [
"related",
"third-party-advisory"
],
"url": "https://cna.erlef.org/cves/CVE-2026-119434.html"
},
{
"name": "OSV record EEF-CVE-2026-119434",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-119434"
},
{
"name": "Introducing commit 6730128 in silviucpp/ezstd",
"tags": [
"related"
],
"url": "https://github.com/silviucpp/ezstd/commit/6730128b1d35eca02a2028671421471ff651f423"
},
{
"name": "Fix commit e34f4a4 in silviucpp/ezstd",
"tags": [
"patch"
],
"url": "https://github.com/silviucpp/ezstd/commit/e34f4a4f6dc18d990d1cece2d5fceef8e934a195"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Decompression bomb in ezstd:decompress_using_ddict/2 allows BEAM memory exhaustion via unbounded output buffer",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eReplace calls to \u003ccode\u003eezstd:decompress_using_ddict/2\u003c/code\u003e with the streaming API: create a context with \u003ccode\u003eezstd:create_decompression_context/1\u003c/code\u003e using a small output buffer, attach the dictionary with \u003ccode\u003eezstd:select_ddict/2\u003c/code\u003e, and decompress with \u003ccode\u003eezstd:decompress_streaming/2\u003c/code\u003e. The streaming path decompresses into the caller-sized buffer and gives up with \u003ccode\u003e{error, decompressor_stuck}\u003c/code\u003e after 1000 chunks, so a 64 KiB buffer caps a single call at 64 MiB of output. Treat that error as a rejected input. Legitimate frames whose output exceeds 1000 times the buffer size fail the same way, so size the buffer for the largest expected payload.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Replace calls to `ezstd:decompress_using_ddict/2` with the streaming API: create a context with `ezstd:create_decompression_context/1` using a small output buffer, attach the dictionary with `ezstd:select_ddict/2`, and decompress with `ezstd:decompress_streaming/2`. The streaming path decompresses into the caller-sized buffer and gives up with `{error, decompressor_stuck}` after 1000 chunks, so a 64 KiB buffer caps a single call at 64 MiB of output. Treat that error as a rejected input. Legitimate frames whose output exceeds 1000 times the buffer size fail the same way, so size the buffer for the largest expected payload."
}
],
"value": "Replace calls to ezstd:decompress_using_ddict/2 with the streaming API: create a context with ezstd:create_decompression_context/1 using a small output buffer, attach the dictionary with ezstd:select_ddict/2, and decompress with ezstd:decompress_streaming/2. The streaming path decompresses into the caller-sized buffer and gives up with {error, decompressor_stuck} after 1000 chunks, so a 64 KiB buffer caps a single call at 64 MiB of output. Treat that error as a rejected input. Legitimate frames whose output exceeds 1000 times the buffer size fail the same way, so size the buffer for the largest expected payload."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eBuild ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB.\u003c/li\u003e\n\u003cli\u003eCreate any decompression dictionary with \u003ccode\u003eezstd:create_ddict/1\u003c/code\u003e; its content does not matter because the frame carries no dictionary ID.\u003c/li\u003e\n\u003cli\u003eConstruct a zstd frame: the magic number, a frame header descriptor selecting an 8-byte content size, a window descriptor, a little-endian content size of 2 GiB, then 16,383 RLE blocks of 128 KiB each (block header \u003ccode\u003e0x02 0x00 0x10\u003c/code\u003e followed by one payload byte) and a final RLE block with the last-block bit set. The frame is 65,550 bytes.\u003c/li\u003e\n\u003cli\u003eCall \u003ccode\u003eezstd:decompress/1\u003c/code\u003e on the frame: it returns \u003ccode\u003e{error, \u0026lt;\u0026lt;\"frame too large or invalid\"\u0026gt;\u0026gt;}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eCall \u003ccode\u003eezstd:decompress_using_ddict/2\u003c/code\u003e on the same frame with the dictionary: the VM is killed by the operating system (exit status 137).\u003c/li\u003e\n\u003cli\u003eRepeat on 1.2.5: the call returns \u003ccode\u003e{error, \u0026lt;\u0026lt;\"frame too large or invalid\"\u0026gt;\u0026gt;}\u003c/code\u003e and the VM continues.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Build ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB.\n2. Create any decompression dictionary with `ezstd:create_ddict/1`; its content does not matter because the frame carries no dictionary ID.\n3. Construct a zstd frame: the magic number, a frame header descriptor selecting an 8-byte content size, a window descriptor, a little-endian content size of 2 GiB, then 16,383 RLE blocks of 128 KiB each (block header `0x02 0x00 0x10` followed by one payload byte) and a final RLE block with the last-block bit set. The frame is 65,550 bytes.\n4. Call `ezstd:decompress/1` on the frame: it returns `{error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e}`.\n5. Call `ezstd:decompress_using_ddict/2` on the same frame with the dictionary: the VM is killed by the operating system (exit status 137).\n6. Repeat on 1.2.5: the call returns `{error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e}` and the VM continues."
}
],
"value": "* Build ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB.\n* Create any decompression dictionary with ezstd:create_ddict/1; its content does not matter because the frame carries no dictionary ID.\n* Construct a zstd frame: the magic number, a frame header descriptor selecting an 8-byte content size, a window descriptor, a little-endian content size of 2 GiB, then 16,383 RLE blocks of 128 KiB each (block header 0x02 0x00 0x10 followed by one payload byte) and a final RLE block with the last-block bit set. The frame is 65,550 bytes.\n* Call ezstd:decompress/1 on the frame: it returns {error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e}.\n* Call ezstd:decompress_using_ddict/2 on the same frame with the dictionary: the VM is killed by the operating system (exit status 137).\n* Repeat on 1.2.5: the call returns {error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e} and the VM continues."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Declared size is attacker controlled.\u003c/strong\u003e A zstd frame header carries an optional Frame Content Size field. \u003ccode\u003ezstd_nif_decompress_using_ddict\u003c/code\u003e in \u003ccode\u003ec_src/ezstd_nif.cc\u003c/code\u003e reads it with \u003ccode\u003eZSTD_getFrameContentSize\u003c/code\u003e and rejects only the two sentinel values \u003ccode\u003eZSTD_CONTENTSIZE_UNKNOWN\u003c/code\u003e and \u003ccode\u003eZSTD_CONTENTSIZE_ERROR\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unbounded allocation.\u003c/strong\u003e The size is passed directly to \u003ccode\u003eenif_make_new_binary\u003c/code\u003e, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling \u003ccode\u003ezstd_nif_decompress\u003c/code\u003e rejects any declared size above \u003ccode\u003eMAX_BUFFER_SIZE\u003c/code\u003e (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Amplification.\u003c/strong\u003e A frame built from RLE blocks, each encoding 128 KiB of a repeated byte in four bytes, declares and genuinely decompresses to 2 GiB from 65,550 bytes of input, a ratio of about 32,760:1. \u003ccode\u003eZSTD_decompress_usingDDict\u003c/code\u003e then fills the buffer. Because this happens inside a NIF, exceeding the memory available to the VM results in the operating system killing the whole BEAM process rather than an Erlang-level exception.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Declared size is attacker controlled.** A zstd frame header carries an optional Frame Content Size field. `zstd_nif_decompress_using_ddict` in `c_src/ezstd_nif.cc` reads it with `ZSTD_getFrameContentSize` and rejects only the two sentinel values `ZSTD_CONTENTSIZE_UNKNOWN` and `ZSTD_CONTENTSIZE_ERROR`.\n\n**2. Unbounded allocation.** The size is passed directly to `enif_make_new_binary`, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling `zstd_nif_decompress` rejects any declared size above `MAX_BUFFER_SIZE` (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.\n\n**3. Amplification.** A frame built from RLE blocks, each encoding 128 KiB of a repeated byte in four bytes, declares and genuinely decompresses to 2 GiB from 65,550 bytes of input, a ratio of about 32,760:1. `ZSTD_decompress_usingDDict` then fills the buffer. Because this happens inside a NIF, exceeding the memory available to the VM results in the operating system killing the whole BEAM process rather than an Erlang-level exception."
}
],
"value": "1. Declared size is attacker controlled. A zstd frame header carries an optional Frame Content Size field. zstd_nif_decompress_using_ddict in c_src/ezstd_nif.cc reads it with ZSTD_getFrameContentSize and rejects only the two sentinel values ZSTD_CONTENTSIZE_UNKNOWN and ZSTD_CONTENTSIZE_ERROR.\n\n2. Unbounded allocation. The size is passed directly to enif_make_new_binary, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling zstd_nif_decompress rejects any declared size above MAX_BUFFER_SIZE (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.\n\n3. Amplification. A frame built from RLE blocks, each encoding 128 KiB of a repeated byte in four bytes, declares and genuinely decompresses to 2 GiB from 65,550 bytes of input, a ratio of about 32,760:1. ZSTD_decompress_usingDDict then fills the buffer. Because this happens inside a NIF, exceeding the memory available to the VM results in the operating system killing the whole BEAM process rather than an Erlang-level exception."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-119434",
"datePublished": "2026-11-05T20:15:56.311Z",
"dateReserved": "2026-10-30T10:30:01.015Z",
"dateUpdated": "2026-11-05T20:45:33.490Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-119434 (GCVE-0-2026-119434)
Vulnerability from cvelistv5 – Published: 2026-11-05 20:15 – Updated: 2026-11-05 20:45
VLAI
EPSS
VEX
Title
Decompression bomb in ezstd:decompress_using_ddict/2 allows BEAM memory exhaustion via unbounded output buffer
Summary
Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to ezstd:decompress_using_ddict/2 to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.
zstd_nif_decompress_using_ddict in c_src/ezstd_nif.cc reads the frame's declared content size with ZSTD_getFrameContentSize and passes it directly to enif_make_new_binary, without the 1 GiB MAX_BUFFER_SIZE check that ezstd:decompress/1 applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by ezstd:decompress/1 but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.
This issue affects ezstd: from 1.0.5 before 1.2.5.
Severity
6.2 (Medium)
SSVC
Exploitation: none
Automatable: no
Technical Impact: partial
CISA Coordinator · CISA-ADP (v2.0.3)
Decision recorded 2026-11-05 20:45 UTC
CWE
- CWE-409 - Improper Handling of Highly Compressed Data (Data Amplification)
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/silviucpp/ezstd/releases/tag/v1.2.5 | release-notesrelated |
| https://cna.erlef.org/cves/CVE-2026-119434.html | relatedthird-party-advisory |
| https://osv.dev/vulnerability/EEF-CVE-2026-119434 | related |
| https://github.com/silviucpp/ezstd/commit/6730128… | related |
| https://github.com/silviucpp/ezstd/commit/e34f4a4… | patch |
Impacted products
{
"containers": {
"adp": [
{
"metrics": [
{
"cvssV3_1": {
"attackComplexity": "LOW",
"attackVector": "LOCAL",
"availabilityImpact": "HIGH",
"baseScore": 6.2,
"baseSeverity": "MEDIUM",
"confidentialityImpact": "NONE",
"integrityImpact": "NONE",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"version": "3.1"
}
},
{
"other": {
"content": {
"id": "CVE-2026-119434",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-11-05T20:45:27.204341Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-11-05T20:45:33.490Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:silviucpp:ezstd:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"ezstd_nif",
"ezstd"
],
"packageName": "ezstd",
"packageURL": "pkg:hex/ezstd",
"product": "ezstd",
"programFiles": [
"c_src/ezstd_nif.cc",
"src/ezstd_nif.erl",
"src/ezstd.erl"
],
"programRoutines": [
{
"name": "zstd_nif_decompress_using_ddict"
},
{
"name": "ezstd_nif:decompress_using_ddict/2"
},
{
"name": "ezstd:decompress_using_ddict/2"
}
],
"repo": "https://github.com/silviucpp/ezstd",
"vendor": "silviucpp",
"versions": [
{
"lessThan": "1.2.5",
"status": "affected",
"version": "1.0.5",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:silviucpp:ezstd:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"ezstd_nif",
"ezstd"
],
"packageName": "silviucpp/ezstd",
"packageURL": "pkg:github/silviucpp/ezstd",
"product": "ezstd",
"programFiles": [
"c_src/ezstd_nif.cc",
"src/ezstd_nif.erl",
"src/ezstd.erl"
],
"programRoutines": [
{
"name": "zstd_nif_decompress_using_ddict"
},
{
"name": "ezstd_nif:decompress_using_ddict/2"
},
{
"name": "ezstd:decompress_using_ddict/2"
}
],
"repo": "https://github.com/silviucpp/ezstd",
"vendor": "silviucpp",
"versions": [
{
"lessThan": "e34f4a4f6dc18d990d1cece2d5fceef8e934a195",
"status": "affected",
"version": "6730128b1d35eca02a2028671421471ff651f423",
"versionType": "git"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:silviucpp:ezstd:*:*:*:*:*:*:*:*",
"versionEndExcluding": "1.2.5",
"versionStartIncluding": "1.0.5",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Oz Kaiser"
},
{
"lang": "en",
"type": "reporter",
"value": "Oz Kaiser"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Silviu Caragea"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-11-04T17:02:50.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to \u003ccode\u003eezstd:decompress_using_ddict/2\u003c/code\u003e to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ezstd_nif_decompress_using_ddict\u003c/code\u003e in \u003ccode\u003ec_src/ezstd_nif.cc\u003c/code\u003e reads the frame\u0027s declared content size with \u003ccode\u003eZSTD_getFrameContentSize\u003c/code\u003e and passes it directly to \u003ccode\u003eenif_make_new_binary\u003c/code\u003e, without the 1 GiB \u003ccode\u003eMAX_BUFFER_SIZE\u003c/code\u003e check that \u003ccode\u003eezstd:decompress/1\u003c/code\u003e applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by \u003ccode\u003eezstd:decompress/1\u003c/code\u003e but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.\u003c/p\u003e\n\u003cp\u003eThis issue affects ezstd: from 1.0.5 before 1.2.5.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to `ezstd:decompress_using_ddict/2` to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.\n\n`zstd_nif_decompress_using_ddict` in `c_src/ezstd_nif.cc` reads the frame\u0027s declared content size with `ZSTD_getFrameContentSize` and passes it directly to `enif_make_new_binary`, without the 1 GiB `MAX_BUFFER_SIZE` check that `ezstd:decompress/1` applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by `ezstd:decompress/1` but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.\n\nThis issue affects ezstd: from 1.0.5 before 1.2.5."
}
],
"value": "Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to ezstd:decompress_using_ddict/2 to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.\n\nzstd_nif_decompress_using_ddict in c_src/ezstd_nif.cc reads the frame\u0027s declared content size with ZSTD_getFrameContentSize and passes it directly to enif_make_new_binary, without the 1 GiB MAX_BUFFER_SIZE check that ezstd:decompress/1 applies before allocating. A valid 65,550-byte frame of RLE blocks declaring 2 GiB is rejected by ezstd:decompress/1 but is fully allocated and decompressed by the dictionary path. The allocation happens inside a NIF, so when it exceeds the memory available to the VM the operating system kills the whole node rather than the calling process.\n\nThis issue affects ezstd: from 1.0.5 before 1.2.5."
}
],
"impacts": [
{
"capecId": "CAPEC-130",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker who can submit a zstd frame to a service that decompresses it with a fixed dictionary can crash the entire Erlang VM with a single request of about 65 KiB. Every application and process hosted in that VM is taken down, not only the one that called the decompressor.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "An attacker who can submit a zstd frame to a service that decompresses it with a fixed dictionary can crash the entire Erlang VM with a single request of about 65 KiB. Every application and process hosted in that VM is taken down, not only the one that called the decompressor."
}
],
"value": "An attacker who can submit a zstd frame to a service that decompresses it with a fixed dictionary can crash the entire Erlang VM with a single request of about 65 KiB. Every application and process hosted in that VM is taken down, not only the one that called the decompressor."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "LOCAL",
"baseScore": 6.9,
"baseSeverity": "MEDIUM",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-409",
"description": "CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-11-05T20:15:56.311Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "ezstd v1.2.5 release notes",
"tags": [
"release-notes",
"related"
],
"url": "https://github.com/silviucpp/ezstd/releases/tag/v1.2.5"
},
{
"name": "EEF CNA record for CVE-2026-119434",
"tags": [
"related",
"third-party-advisory"
],
"url": "https://cna.erlef.org/cves/CVE-2026-119434.html"
},
{
"name": "OSV record EEF-CVE-2026-119434",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-119434"
},
{
"name": "Introducing commit 6730128 in silviucpp/ezstd",
"tags": [
"related"
],
"url": "https://github.com/silviucpp/ezstd/commit/6730128b1d35eca02a2028671421471ff651f423"
},
{
"name": "Fix commit e34f4a4 in silviucpp/ezstd",
"tags": [
"patch"
],
"url": "https://github.com/silviucpp/ezstd/commit/e34f4a4f6dc18d990d1cece2d5fceef8e934a195"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Decompression bomb in ezstd:decompress_using_ddict/2 allows BEAM memory exhaustion via unbounded output buffer",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eReplace calls to \u003ccode\u003eezstd:decompress_using_ddict/2\u003c/code\u003e with the streaming API: create a context with \u003ccode\u003eezstd:create_decompression_context/1\u003c/code\u003e using a small output buffer, attach the dictionary with \u003ccode\u003eezstd:select_ddict/2\u003c/code\u003e, and decompress with \u003ccode\u003eezstd:decompress_streaming/2\u003c/code\u003e. The streaming path decompresses into the caller-sized buffer and gives up with \u003ccode\u003e{error, decompressor_stuck}\u003c/code\u003e after 1000 chunks, so a 64 KiB buffer caps a single call at 64 MiB of output. Treat that error as a rejected input. Legitimate frames whose output exceeds 1000 times the buffer size fail the same way, so size the buffer for the largest expected payload.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Replace calls to `ezstd:decompress_using_ddict/2` with the streaming API: create a context with `ezstd:create_decompression_context/1` using a small output buffer, attach the dictionary with `ezstd:select_ddict/2`, and decompress with `ezstd:decompress_streaming/2`. The streaming path decompresses into the caller-sized buffer and gives up with `{error, decompressor_stuck}` after 1000 chunks, so a 64 KiB buffer caps a single call at 64 MiB of output. Treat that error as a rejected input. Legitimate frames whose output exceeds 1000 times the buffer size fail the same way, so size the buffer for the largest expected payload."
}
],
"value": "Replace calls to ezstd:decompress_using_ddict/2 with the streaming API: create a context with ezstd:create_decompression_context/1 using a small output buffer, attach the dictionary with ezstd:select_ddict/2, and decompress with ezstd:decompress_streaming/2. The streaming path decompresses into the caller-sized buffer and gives up with {error, decompressor_stuck} after 1000 chunks, so a 64 KiB buffer caps a single call at 64 MiB of output. Treat that error as a rejected input. Legitimate frames whose output exceeds 1000 times the buffer size fail the same way, so size the buffer for the largest expected payload."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eBuild ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB.\u003c/li\u003e\n\u003cli\u003eCreate any decompression dictionary with \u003ccode\u003eezstd:create_ddict/1\u003c/code\u003e; its content does not matter because the frame carries no dictionary ID.\u003c/li\u003e\n\u003cli\u003eConstruct a zstd frame: the magic number, a frame header descriptor selecting an 8-byte content size, a window descriptor, a little-endian content size of 2 GiB, then 16,383 RLE blocks of 128 KiB each (block header \u003ccode\u003e0x02 0x00 0x10\u003c/code\u003e followed by one payload byte) and a final RLE block with the last-block bit set. The frame is 65,550 bytes.\u003c/li\u003e\n\u003cli\u003eCall \u003ccode\u003eezstd:decompress/1\u003c/code\u003e on the frame: it returns \u003ccode\u003e{error, \u0026lt;\u0026lt;\"frame too large or invalid\"\u0026gt;\u0026gt;}\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eCall \u003ccode\u003eezstd:decompress_using_ddict/2\u003c/code\u003e on the same frame with the dictionary: the VM is killed by the operating system (exit status 137).\u003c/li\u003e\n\u003cli\u003eRepeat on 1.2.5: the call returns \u003ccode\u003e{error, \u0026lt;\u0026lt;\"frame too large or invalid\"\u0026gt;\u0026gt;}\u003c/code\u003e and the VM continues.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Build ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB.\n2. Create any decompression dictionary with `ezstd:create_ddict/1`; its content does not matter because the frame carries no dictionary ID.\n3. Construct a zstd frame: the magic number, a frame header descriptor selecting an 8-byte content size, a window descriptor, a little-endian content size of 2 GiB, then 16,383 RLE blocks of 128 KiB each (block header `0x02 0x00 0x10` followed by one payload byte) and a final RLE block with the last-block bit set. The frame is 65,550 bytes.\n4. Call `ezstd:decompress/1` on the frame: it returns `{error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e}`.\n5. Call `ezstd:decompress_using_ddict/2` on the same frame with the dictionary: the VM is killed by the operating system (exit status 137).\n6. Repeat on 1.2.5: the call returns `{error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e}` and the VM continues."
}
],
"value": "* Build ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB.\n* Create any decompression dictionary with ezstd:create_ddict/1; its content does not matter because the frame carries no dictionary ID.\n* Construct a zstd frame: the magic number, a frame header descriptor selecting an 8-byte content size, a window descriptor, a little-endian content size of 2 GiB, then 16,383 RLE blocks of 128 KiB each (block header 0x02 0x00 0x10 followed by one payload byte) and a final RLE block with the last-block bit set. The frame is 65,550 bytes.\n* Call ezstd:decompress/1 on the frame: it returns {error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e}.\n* Call ezstd:decompress_using_ddict/2 on the same frame with the dictionary: the VM is killed by the operating system (exit status 137).\n* Repeat on 1.2.5: the call returns {error, \u003c\u003c\"frame too large or invalid\"\u003e\u003e} and the VM continues."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Declared size is attacker controlled.\u003c/strong\u003e A zstd frame header carries an optional Frame Content Size field. \u003ccode\u003ezstd_nif_decompress_using_ddict\u003c/code\u003e in \u003ccode\u003ec_src/ezstd_nif.cc\u003c/code\u003e reads it with \u003ccode\u003eZSTD_getFrameContentSize\u003c/code\u003e and rejects only the two sentinel values \u003ccode\u003eZSTD_CONTENTSIZE_UNKNOWN\u003c/code\u003e and \u003ccode\u003eZSTD_CONTENTSIZE_ERROR\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unbounded allocation.\u003c/strong\u003e The size is passed directly to \u003ccode\u003eenif_make_new_binary\u003c/code\u003e, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling \u003ccode\u003ezstd_nif_decompress\u003c/code\u003e rejects any declared size above \u003ccode\u003eMAX_BUFFER_SIZE\u003c/code\u003e (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Amplification.\u003c/strong\u003e A frame built from RLE blocks, each encoding 128 KiB of a repeated byte in four bytes, declares and genuinely decompresses to 2 GiB from 65,550 bytes of input, a ratio of about 32,760:1. \u003ccode\u003eZSTD_decompress_usingDDict\u003c/code\u003e then fills the buffer. Because this happens inside a NIF, exceeding the memory available to the VM results in the operating system killing the whole BEAM process rather than an Erlang-level exception.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Declared size is attacker controlled.** A zstd frame header carries an optional Frame Content Size field. `zstd_nif_decompress_using_ddict` in `c_src/ezstd_nif.cc` reads it with `ZSTD_getFrameContentSize` and rejects only the two sentinel values `ZSTD_CONTENTSIZE_UNKNOWN` and `ZSTD_CONTENTSIZE_ERROR`.\n\n**2. Unbounded allocation.** The size is passed directly to `enif_make_new_binary`, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling `zstd_nif_decompress` rejects any declared size above `MAX_BUFFER_SIZE` (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.\n\n**3. Amplification.** A frame built from RLE blocks, each encoding 128 KiB of a repeated byte in four bytes, declares and genuinely decompresses to 2 GiB from 65,550 bytes of input, a ratio of about 32,760:1. `ZSTD_decompress_usingDDict` then fills the buffer. Because this happens inside a NIF, exceeding the memory available to the VM results in the operating system killing the whole BEAM process rather than an Erlang-level exception."
}
],
"value": "1. Declared size is attacker controlled. A zstd frame header carries an optional Frame Content Size field. zstd_nif_decompress_using_ddict in c_src/ezstd_nif.cc reads it with ZSTD_getFrameContentSize and rejects only the two sentinel values ZSTD_CONTENTSIZE_UNKNOWN and ZSTD_CONTENTSIZE_ERROR.\n\n2. Unbounded allocation. The size is passed directly to enif_make_new_binary, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling zstd_nif_decompress rejects any declared size above MAX_BUFFER_SIZE (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.\n\n3. Amplification. A frame built from RLE blocks, each encoding 128 KiB of a repeated byte in four bytes, declares and genuinely decompresses to 2 GiB from 65,550 bytes of input, a ratio of about 32,760:1. ZSTD_decompress_usingDDict then fills the buffer. Because this happens inside a NIF, exceeding the memory available to the VM results in the operating system killing the whole BEAM process rather than an Erlang-level exception."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-119434",
"datePublished": "2026-11-05T20:15:56.311Z",
"dateReserved": "2026-10-30T10:30:01.015Z",
"dateUpdated": "2026-11-05T20:45:33.490Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}