CVE-2026-119434: Decompression bomb in ezstd:decompress_using_ddict/2 allows BEAM memory exhaustion via unbounded output buffer

Published Nov 5, 2026
·
Updated

Summary

Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to ezstd:decompressusingddict/2 to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.

zstdnifdecompressusingddict in csrc/ezstdnif.cc reads the frame's declared content size with ZSTDgetFrameContentSize and passes it directly to enifmakenewbinary, without the 1 GiB MAXBUFFERSIZE 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.

Details

1. Declared size is attacker controlled. A zstd frame header carries an optional Frame Content Size field. zstdnifdecompressusingddict in csrc/ezstdnif.cc reads it with ZSTDgetFrameContentSize and rejects only the two sentinel values ZSTDCONTENTSIZEUNKNOWN and ZSTDCONTENTSIZEERROR.

2. Unbounded allocation. The size is passed directly to enifmakenewbinary, so a frame declaring 2 GiB makes the NIF request a 2 GiB binary. The sibling zstdnifdecompress rejects any declared size above MAXBUFFERSIZE (1 GiB) before allocating; the dictionary path omitted that check from the commit that introduced it.

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. ZSTDdecompressusingDDict 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.

Proof of concept

1. Build ezstd 1.2.4 and start a VM under an external memory limit, for example a container limited to 256 MiB. 2. Create any decompression dictionary with ezstd:createddict/1; its content does not matter because the frame carries no dictionary ID. 3. 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. 4. Call ezstd:decompress/1 on the frame: it returns {error, <<"frame too large or invalid">>}. 5. Call ezstd:decompressusingddict/2 on the same frame with the dictionary: the VM is killed by the operating system (exit status 137). 6. Repeat on 1.2.5: the call returns {error, <<"frame too large or invalid">>} and the VM continues.

Impact

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.

Workarounds

Replace calls to ezstd:decompressusingddict/2 with the streaming API: create a context with ezstd:createdecompressioncontext/1 using a small output buffer, attach the dictionary with ezstd:selectddict/2, and decompress with ezstd:decompressstreaming/2. The streaming path decompresses into the caller-sized buffer and gives up with {error, decompressorstuck} 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.

Other sources

Improper Handling of Highly Compressed Data (Data Amplification) vulnerability in silviucpp ezstd allows an attacker who supplies a zstd frame to ezstd:decompressusingddict/2 to exhaust memory and terminate the Erlang VM via a small frame that declares a very large content size.

zstdnifdecompressusingddict in csrc/ezstdnif.cc reads the frame's declared content size with ZSTDgetFrameContentSize and passes it directly to enifmakenewbinary, without the 1 GiB MAXBUFFERSIZE 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.

— MITRE

Affected Software

1 affected componentFixes available
erlang/ezstd>=1.0.5<1.2.5
1.2.5

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade erlang/ezstd to a version that resolves this vulnerability.

    Fixed in 1.2.5
  2. Upgrade

    Upgrade ezstd to a version that resolves this vulnerability.

    Fixed in 1.2.5
  3. Compensating control

    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 such as 64 KiB, attach the dictionary with ezstd:select_ddict/2, and decompress with ezstd:decompress_streaming/2. Treat {error, decompressor_stuck} as rejected input and size the buffer for the largest expected payload.

Event History

Nov 5, 2026
CVE Published
via MITRE·08:15 PM
Data Sourced
via MITRE·08:15 PM
DescriptionWeakness
Data Sourced
via OSV·08:15 PM
DescriptionSeverityWeaknessAffected Software
Data Sourced
via NVD·09:18 PM
DescriptionSeverityWeakness

Frequently Asked Questions

1

Which deployments are exposed to this issue?

Deployments using ezstd versions from 1.0.5 before 1.2.5 are affected when they pass attacker-supplied zstd frames to ezstd:decompress_using_ddict/2. The non-dictionary ezstd:decompress/1 path applies a 1 GiB maximum-buffer check and rejects the described frame.

2

What does an attacker need to do to trigger the denial of service?

An attacker needs to supply a crafted zstd frame to ezstd:decompress_using_ddict/2. The frame can be small but declare a very large content size, causing the NIF to allocate an unbounded output binary.

3

What is the impact if the allocation exceeds available memory?

The operating system can kill the entire Erlang VM/node because the allocation occurs inside a NIF. This is not limited to terminating the process that invoked decompression.

4

How can I tell whether my application is using the vulnerable path?

Review calls that process untrusted compressed data and identify use of ezstd:decompress_using_ddict/2. Systems using affected versions but only ezstd:decompress/1 have the documented 1 GiB buffer-size check on that path.

5

What should be done if an immediate upgrade is not possible?

Do not pass untrusted zstd frames to ezstd:decompress_using_ddict/2. Use the checked ezstd:decompress/1 path where applicable, which rejects the described oversized declared content size.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203