CVE-2026-53430
Description
The Elixir gRPC library before 1.0.0 allows unauthenticated remote attackers to crash servers via a gzip decompression bomb.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
The Elixir gRPC library before 1.0.0 allows unauthenticated remote attackers to crash servers via a gzip decompression bomb.
Vulnerability
CVE-2026-53430 is a denial of service vulnerability in the elixir-grpc grpc library (versions 0.4.0 up to, but not including, 1.0.0). The issue resides in GRPC.Compressor.Gzip.decompress/1 (file lib/grpc/compressor/gzip.ex) and GRPC.Message.from_data/2 (lib/grpc/message.ex). The decompress/1 function calls :zlib.gunzip/1 directly on attacker-controlled bytes without any decompressed-size limit, compression ratio check, or incremental decoding [1][2]. Because this module is registered as the gzip compressor for the gRPC framework, it is automatically invoked whenever an incoming gRPC frame carries the grpc-encoding: gzip header. The server's max_receive_message_length is enforced only against the already-decompressed message, so it provides no protection against this amplification [1].
Exploitation
An unauthenticated remote attacker simply needs network access to the gRPC endpoint and the ability to send a single crafted gRPC frame. The attacker constructs a highly compressible payload (e.g., a few kilobytes of zeros, which gzip compresses at roughly 1000:1) [1][3]. Sending this frame with grpc-encoding: gzip triggers the vulnerable decompression routine. :zlib.gunzip/1 allocates the entire decompressed result as a single binary, expanding the small payload to multiple gigabytes in a single function call [1]. No user interaction, authentication, or prior state is required.
Impact
Successful exploitation exhausts the BEAM node's heap, causing an out-of-memory (OOM) kill and resulting in a denial of service (DoS) for the gRPC server [1][2][3]. The impact is limited to availability; no data confidentiality or integrity is compromised. The CVSS v4.0 score is 8.7 (High) with vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N [3].
Mitigation
The vulnerability is fixed in version 1.0.0 of the grpc package [1][2]. The fix applies incremental decompression with chunked input (8 KB slices) and enforces a configurable max_decompressed_message_length (default 4 MB) [4]. Any service using the library should upgrade to version 1.0.0 or later. For those unable to upgrade immediately, disabling gzip compression on the server side or applying network-layer filtering to drop suspiciously small compressed frames may temporarily reduce risk, but no official workaround is provided in the reference advisories [1].
AI Insight generated on Jun 15, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: >=0.4.0, <1.0.0
Patches
11afbab9d57d2fix: mitigate zip bomb (#543)
2 files changed · +92 −1
grpc_core/lib/grpc/compressor/gzip.ex+42 −1 modified@@ -1,6 +1,14 @@ defmodule GRPC.Compressor.Gzip do @behaviour GRPC.Compressor + # 4 MB – matches gRPC-Go's default max receive message size. + # Override with: Application.put_env(:grpc, :max_decompressed_message_length, bytes) + @default_max_decompressed_size 4 * 1024 * 1024 + + # Feed compressed input in 8 KB slices so we can detect an oversized output + # before allocating the full decompressed payload. + @input_chunk_size 8_192 + def name do "gzip" end @@ -10,6 +18,39 @@ defmodule GRPC.Compressor.Gzip do end def decompress(data) do - :zlib.gunzip(data) + max_size = + Application.get_env(:grpc, :max_decompressed_message_length, @default_max_decompressed_size) + + z = :zlib.open() + # windowBits 31 = 15 (max window) + 16 (gzip format flag) + :ok = :zlib.inflateInit(z, 31) + + try do + chunks = inflate_chunks(z, data, max_size, 0, []) + :zlib.inflateEnd(z) + IO.iodata_to_binary(chunks) + after + :zlib.close(z) + end end + + defp inflate_chunks(_z, <<>>, _max_size, _acc_size, acc), do: acc + + defp inflate_chunks(z, data, max_size, acc_size, acc) do + {chunk, rest} = split_chunk(data) + output = :zlib.inflate(z, chunk) + new_size = acc_size + IO.iodata_length(output) + + if new_size > max_size do + raise GRPC.RPCError, + status: :resource_exhausted, + message: "Decompressed message exceeds limit of #{max_size} bytes" + end + + inflate_chunks(z, rest, max_size, new_size, [acc, output]) + end + + defp split_chunk(data) when byte_size(data) <= @input_chunk_size, do: {data, <<>>} + + defp split_chunk(<<chunk::bytes-size(@input_chunk_size), rest::binary>>), do: {chunk, rest} end
grpc_core/test/grpc/compressor/gzip_test.exs+50 −0 added@@ -0,0 +1,50 @@ +defmodule GRPC.Compressor.GzipTest do + use ExUnit.Case, async: true + + alias GRPC.Compressor.Gzip + + test "round-trips small payloads correctly" do + data = String.duplicate("hello world ", 50) + assert Gzip.decompress(Gzip.compress(data)) == data + end + + test "raises GRPC.RPCError :resource_exhausted on decompression bomb" do + # Build a small gzip payload that decompresses to 2x the default 4 MB limit. + bomb_size = 9 * 1024 * 1024 + compressed = :zlib.gzip(:binary.copy(<<0>>, bomb_size)) + + assert_raise GRPC.RPCError, ~r/exceeds limit/, fn -> + Gzip.decompress(compressed) + end + end + + test "respects :max_decompressed_message_length application env" do + # Temporarily lower the limit to 1 KB. + Application.put_env(:grpc, :max_decompressed_message_length, 1024) + + on_exit(fn -> + Application.delete_env(:grpc, :max_decompressed_message_length) + end) + + oversized = :zlib.gzip(:binary.copy(<<0>>, 2048)) + + assert_raise GRPC.RPCError, ~r/exceeds limit/, fn -> + Gzip.decompress(oversized) + end + end + + test "accepts payload exactly at the configured limit" do + limit = 4096 + Application.put_env(:grpc, :max_decompressed_message_length, limit) + + on_exit(fn -> + Application.delete_env(:grpc, :max_decompressed_message_length) + end) + + # Use incompressible data so the decompressed size is exactly `limit`. + data = :crypto.strong_rand_bytes(limit) + compressed = :zlib.gzip(data) + + assert Gzip.decompress(compressed) == data + end +end
Vulnerability mechanics
Root cause
"Missing decompressed-size limit, ratio check, and incremental decoding in `GRPC.Compressor.Gzip.decompress/1` allows a gzip decompression bomb to exhaust server memory."
Attack vector
An unauthenticated remote attacker sends a single crafted gRPC frame with the `grpc-encoding: gzip` header and a small, highly compressible payload (e.g., a few kilobytes of zeros that gzip compresses at roughly 1000:1) [ref_id=1]. The server's `GRPC.Compressor.Gzip.decompress/1` calls `:zlib.gunzip/1`, which allocates the entire decompressed result as a single binary, expanding the payload to multiple gigabytes and exhausting the BEAM node's heap, triggering an out-of-memory kill [ref_id=1]. No authentication, prior state, or special configuration is required — the attacker only needs to reach the gRPC port [ref_id=1].
Affected code
The vulnerability resides in `lib/grpc/compressor/gzip.ex` in the `GRPC.Compressor.Gzip.decompress/1` function, which called `:zlib.gunzip/1` directly on attacker-controlled bytes with no size limit, ratio check, or incremental decoding [ref_id=1]. Because this module is the registered gzip `GRPC.Compressor` implementation, it is invoked automatically whenever an incoming gRPC frame carries the `grpc-encoding: gzip` header. The `GRPC.Message.from_data/2` function in `lib/grpc/message.ex` is the entry point that triggers the vulnerable decompression path [ref_id=1].
What the fix does
The patch replaces the single `:zlib.gunzip/1` call with incremental decompression using `:zlib.inflate/2` fed in 8 KB chunks [patch_id=6113715]. After each chunk is decompressed, the accumulated output size is checked against a configurable `max_decompressed_message_length` (defaulting to 4 MB) [patch_id=6113715]. If the limit is exceeded, a `GRPC.RPCError` with status `:resource_exhausted` is raised, preventing the full decompression bomb from being allocated [patch_id=6113715]. This ensures the server detects oversized decompressed data before exhausting memory.
Preconditions
- networkThe attacker must be able to reach the gRPC port of a server built on this library.
- configThe server must accept gzip-compressed requests (default behavior when the compressor is registered).
Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.