EEF-CVE-2026-119434

    Dashboard / Vulnerabilities / EEF-CVE-2026-119434

    EEF-CVE-2026-119434

    Published: 5 Nov 2026Last Modified: 6 Nov 2026

    Summary: Decompression bomb in ezstd:decompress_using_ddict/2 allows BEAM memory exhaustion via unbounded output buffer

    Details: ## 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. ## Details **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`. **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. **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. ## 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:create_ddict/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:decompress_using_ddict/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: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.

    Affected packages

    Package

    Name: ezstd

    Purl: pkg:hex/ezstd

    Affected ranges

    Type: SEMVER

    Events:

    Introduced- 1.0.5
    Fixed -1.2.5

    Affected versions

    1.0.5
    1.0.6
    1.0.7
    1.0.8
    1.0.9

    Common Vulnerability Scoring System

    Attack Vector
    Network
    Adjacent
    Local
    Physical
    Privileges Required
    None
    Low
    High
    User Interaction
    None
    Required
    Scope
    Unchanged
    Changed
    Confidentiality
    None
    Low
    High
    Integrity
    None
    Low
    High
    Availability
    None
    Low
    High