VYPR
Critical severityNVD Advisory· Published Jun 15, 2026

CVE-2026-48853

CVE-2026-48853

Description

Unauthenticated remote attackers can crash the BEAM node or achieve RCE via unsafe Erlang term deserialization in elixir-grpc's erlpack codec.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Unauthenticated remote attackers can crash the BEAM node or achieve RCE via unsafe Erlang term deserialization in elixir-grpc's erlpack codec.

Vulnerability

The Elixir.GRPC.Codec.Erlpack.decode/2 function in lib/grpc/codec/erlpack.ex calls :erlang.binary_to_term/1 on the raw gRPC message body without the :safe option, no size bound, and no type guard [1][2][3][4]. This allows deserialization of untrusted data and allocation of resources without limits. The erlpack codec is not registered by default; it must be explicitly added to the server's codecs option [4]. Affected versions are grpc from 0.4.0 before 1.0.0 [2][3].

Exploitation

An unauthenticated attacker can send an HTTP/2 request to any gRPC endpoint with Content-Type: application/grpc+erlpack and a crafted payload [4]. Two independent exploitation paths exist: (1) encoding a large number of fresh atoms exhausts the bounded atom table (never garbage-collected), crashing the entire BEAM VM; (2) encoding a fun term that, if applied downstream (e.g., via Enum.map, Task.async, or direct invocation), executes attacker-controlled code inside the server process [4]. No authentication or prior access is required [2][3].

Impact

Successful exploitation leads to either denial of service (node-level crash via atom table exhaustion) or remote code execution (RCE) in the server process [1][2][3][4]. The CVSS v4.0 score is 9.2 (Critical) [3]. An attacker can take full control of the affected server or disrupt all applications on the BEAM node.

Mitigation

The vulnerability is fixed in commit 272a97a5ea1b46af1819f14a831fcf35fc91f992 [1], which is included in grpc version 1.0.0 [2][3]. Users should upgrade to grpc >= 1.0.0. As a workaround, if the erlpack codec is not required, do not register GRPC.Codec.Erlpack in the server configuration [4]. No other mitigations are available.

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
  • Elixir Grpc/Grpcreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: >=0.4.0, <1.0.0

Patches

1
272a97a5ea1b

fix: safer decoding for erlpack codec (#540)

https://github.com/elixir-grpc/grpcPaulo ValenteJun 15, 2026via body-scan
15 files changed · +150 32
  • benchmark/mix.exs+1 1 modified
    @@ -23,7 +23,7 @@ defmodule Benchmark.MixProject do
           {:grpc_server, path: "../grpc_server"},
           {:grpc, path: "../grpc"},
           {:gun, "~> 2.0"},
    -      {:protobuf, "~> 0.14"}
    +      {:protobuf, "~> 0.17"}
         ]
       end
     end
    
  • benchmark/mix.lock+3 3 modified
    @@ -6,8 +6,8 @@
       "googleapis": {:hex, :googleapis, "0.1.0", "13770f3f75f5b863fb9acf41633c7bc71bad788f3f553b66481a096d083ee20e", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1989a7244fd17d3eb5f3de311a022b656c3736b39740db46506157c4604bd212"},
       "grpc_core": {:hex, :grpc_core, "1.0.0-rc.1", "d82957bca32937bb52df06596cca7550783acc139a06b70202a982ef8b59490e", [:mix], [{:googleapis, "~> 0.1.0", [hex: :googleapis, repo: "hexpm", optional: false]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c76233ea374421da562b5b022c22614e81f9cf862da93543cff93c37c085f136"},
       "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
    -  "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
    -  "protobuf": {:hex, :protobuf, "0.15.0", "c9fc1e9fc1682b05c601df536d5ff21877b55e2023e0466a3855cc1273b74dcb", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d7bb325319db1d668838d2691c31c7b793c34111aec87d5ee467a39dac6e051"},
    +  "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
    +  "protobuf": {:hex, :protobuf, "0.17.0", "39e24e43c9648e148feba16ed51100b5b2028ea900b55460377b0476f6e10613", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ca6c91f6f63e2c147b47f03eefd10b80538aa6fc55ff4b12b795efb786b0152f"},
       "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
    -  "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
    +  "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
     }
    
  • grpc_core/lib/grpc/codec/erlpack.ex+50 1 modified
    @@ -1,4 +1,21 @@
     defmodule GRPC.Codec.Erlpack do
    +  @moduledoc """
    +  Codec that serializes messages using the Erlang external term format.
    +
    +  Decoding hardens against untrusted gRPC payloads (CVE-2026-48853 /
    +  GHSA-grp7-v8xh-rj7h):
    +
    +    * `:erlang.binary_to_term/2` is called with the `:safe` option, which
    +      prevents the payload from creating new atoms (atom-table exhaustion DoS).
    +    * The decoded term is then rejected if it contains a function, pid, port or
    +      reference. `:safe` alone does not block fun materialization on every OTP
    +      release, and a materialized fun reaching a call site enables remote code
    +      execution. None of these types are valid in a gRPC payload.
    +
    +  As a consequence, any atom referenced by an incoming payload must already
    +  exist in the receiving node, which is the case for loaded protobuf structs.
    +  """
    +
       @behaviour GRPC.Codec
     
       def name() do
    @@ -10,6 +27,38 @@ defmodule GRPC.Codec.Erlpack do
       end
     
       def decode(binary, _module) do
    -    :erlang.binary_to_term(binary)
    +    term = :erlang.binary_to_term(binary, [:safe])
    +    ensure_safe_term!(term)
    +    term
    +  end
    +
    +  defp ensure_safe_term!(term)
    +       when is_function(term) or is_pid(term) or is_port(term) or is_reference(term) do
    +    raise ArgumentError,
    +          "refusing to decode unsafe erlpack payload containing a #{term_type(term)}"
    +  end
    +
    +  defp ensure_safe_term!(term) when is_list(term) do
    +    Enum.each(term, &ensure_safe_term!/1)
    +  end
    +
    +  defp ensure_safe_term!(term) when is_tuple(term) do
    +    term |> Tuple.to_list() |> Enum.each(&ensure_safe_term!/1)
       end
    +
    +  defp ensure_safe_term!(term) when is_map(term) do
    +    # `Map.to_list/1` works for plain maps and structs alike, unlike `Enum`,
    +    # which is not implemented for structs.
    +    Enum.each(Map.to_list(term), fn {key, value} ->
    +      ensure_safe_term!(key)
    +      ensure_safe_term!(value)
    +    end)
    +  end
    +
    +  defp ensure_safe_term!(_term), do: :ok
    +
    +  defp term_type(term) when is_function(term), do: "function"
    +  defp term_type(term) when is_pid(term), do: "pid"
    +  defp term_type(term) when is_port(term), do: "port"
    +  defp term_type(term) when is_reference(term), do: "reference"
     end
    
  • grpc_core/lib/grpc/codec/proto.ex+2 2 modified
    @@ -6,10 +6,10 @@ defmodule GRPC.Codec.Proto do
       end
     
       def encode(struct, _opts \\ []) do
    -    Protobuf.Encoder.encode_to_iodata(struct)
    +    Protobuf.encode_to_iodata(struct)
       end
     
       def decode(binary, module) do
    -    module.decode(binary)
    +    Protobuf.decode(binary, module)
       end
     end
    
  • grpc_core/lib/grpc/codec/web_text.ex+2 2 modified
    @@ -6,7 +6,7 @@ defmodule GRPC.Codec.WebText do
       end
     
       def encode(struct, _opts \\ []) do
    -    Protobuf.Encoder.encode(struct)
    +    Protobuf.encode(struct)
       end
     
       def pack_for_channel(data) when is_list(data) do
    @@ -24,6 +24,6 @@ defmodule GRPC.Codec.WebText do
       end
     
       def decode(binary, module) do
    -    Protobuf.Decoder.decode(binary, module)
    +    Protobuf.decode(binary, module)
       end
     end
    
  • grpc_core/mix.exs+1 1 modified
    @@ -28,7 +28,7 @@ defmodule GRPC.Core.MixProject do
     
       defp deps do
         [
    -      {:protobuf, "~> 0.14"},
    +      {:protobuf, "~> 0.17"},
           {:jason, ">= 0.0.0"},
           {:telemetry, "~> 1.0"},
           {:googleapis, "~> 0.1.0"},
    
  • grpc_core/mix.lock+2 2 modified
    @@ -3,13 +3,13 @@
       "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
       "ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"},
       "googleapis": {:hex, :googleapis, "0.1.0", "13770f3f75f5b863fb9acf41633c7bc71bad788f3f553b66481a096d083ee20e", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1989a7244fd17d3eb5f3de311a022b656c3736b39740db46506157c4604bd212"},
    -  "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
    +  "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
       "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
       "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
       "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
       "makeup_syntect": {:hex, :makeup_syntect, "0.1.3", "ae2c3437f479ea50d08d794acaf02a2f3a8c338dd1f757f6b237c42eb27fcde1", [:mix], [{:makeup, "~> 1.2", [hex: :makeup, repo: "hexpm", optional: false]}, {:rustler, "~> 0.36.1", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8.2", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "a27bd3bd8f7b87465d110295a33ed1022202bea78701bd2bbeadfb45d690cdbf"},
       "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
    -  "protobuf": {:hex, :protobuf, "0.15.0", "c9fc1e9fc1682b05c601df536d5ff21877b55e2023e0466a3855cc1273b74dcb", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d7bb325319db1d668838d2691c31c7b793c34111aec87d5ee467a39dac6e051"},
    +  "protobuf": {:hex, :protobuf, "0.17.0", "39e24e43c9648e148feba16ed51100b5b2028ea900b55460377b0476f6e10613", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ca6c91f6f63e2c147b47f03eefd10b80538aa6fc55ff4b12b795efb786b0152f"},
       "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.3", "4e741024b0b097fe783add06e53ae9a6f23ddc78df1010f215df0c02915ef5a8", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "c23f5f33cb6608542de4d04faf0f0291458c352a4648e4d28d17ee1098cddcc4"},
       "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
     }
    
  • grpc/mix.exs+4 4 modified
    @@ -29,17 +29,17 @@ defmodule GRPC.MixProject do
     
       defp deps do
         [
    -      # {:grpc_core, path: "../grpc_core"},
    +      {:grpc_core, path: "../grpc_core"},
           # Uncomment for hex release
    -      {:grpc_core, "~> 1.0.0-rc.1"},
    +      # {:grpc_core, "~> 1.0.0-rc.1"},
           {:gun, "~> 2.0", optional: true},
           {:mint, "~> 1.5", optional: true},
           {:castore, "~> 0.1 or ~> 1.0", optional: true},
           {:ex_doc, "~> 0.39", only: [:dev, :docs], runtime: false},
           {:ex_parameterized, "~> 1.3.7", only: :test},
           {:mox, "~> 1.2", only: :test},
    -      # {:grpc_server, path: "../grpc_server", only: :test}
    -      {:grpc_server, "~> 1.0.0-rc.1", only: :test}
    +      {:grpc_server, path: "../grpc_server", only: :test}
    +      # {:grpc_server, "~> 1.0.0-rc.1", only: :test}
         ]
       end
     
    
  • grpc/mix.lock+5 5 modified
    @@ -1,7 +1,7 @@
     %{
       "castore": {:hex, :castore, "1.0.16", "8a4f9a7c8b81cda88231a08fe69e3254f16833053b23fa63274b05cbc61d2a1e", [:mix], [], "hexpm", "33689203a0eaaf02fcd0e86eadfbcf1bd636100455350592e7e2628564022aaf"},
    -  "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"},
    -  "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"},
    +  "cowboy": {:hex, :cowboy, "2.16.1", "fa04080b602ff25c40a7700f2dc0152dbc1ba26b42093ae0fa9bb7a337d5a242", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "b8ea4dd317a043e3177ec840cfa3bcb47cfb41035d3abb24d954dc7d51def399"},
    +  "cowlib": {:hex, :cowlib, "2.17.1", "3e6053016d1ab245730f0af688755476dcedb1c25ed8fb5751f59a2bfdc0c9af", [:make, :rebar3], [], "hexpm", "ff08bd17e6dd931445b18af77315b9b5fe052407110964ad2588c686b57b5e3f"},
       "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
       "ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"},
       "ex_parameterized": {:hex, :ex_parameterized, "1.3.7", "801f85fc4651cb51f11b9835864c6ed8c5e5d79b1253506b5bb5421e8ab2f050", [:mix], [], "hexpm", "1fb0dc4aa9e8c12ae23806d03bcd64a5a0fc9cd3f4c5602ba72561c9b54a625c"},
    @@ -12,15 +12,15 @@
       "grpc_server": {:hex, :grpc_server, "1.0.0-rc.1", "d024def6635f23d80966ac3fecaa15f78e4505341d314b373f7fa830ffde568b", [:mix], [{:cowboy, "~> 2.14", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.14", [hex: :cowlib, repo: "hexpm", optional: false]}, {:flow, "~> 1.2", [hex: :flow, repo: "hexpm", optional: false]}, {:grpc_core, "~> 1.0.0-rc.1", [hex: :grpc_core, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "6b3b0aff3097cb59b35ab8a082b557055905f02eadf7f19e4cb5c4ede60f9691"},
       "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
       "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
    -  "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
    +  "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
       "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
       "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
       "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
       "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
       "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
       "nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"},
       "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
    -  "protobuf": {:hex, :protobuf, "0.15.0", "c9fc1e9fc1682b05c601df536d5ff21877b55e2023e0466a3855cc1273b74dcb", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d7bb325319db1d668838d2691c31c7b793c34111aec87d5ee467a39dac6e051"},
    +  "protobuf": {:hex, :protobuf, "0.17.0", "39e24e43c9648e148feba16ed51100b5b2028ea900b55460377b0476f6e10613", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ca6c91f6f63e2c147b47f03eefd10b80538aa6fc55ff4b12b795efb786b0152f"},
       "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
    -  "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
    +  "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
     }
    
  • grpc_server/lib/grpc/protoc/cli.ex+1 1 modified
    @@ -44,7 +44,7 @@ defmodule GRPC.Protoc.CLI do
         # Read the standard input that protoc feeds us.
         bin = binread_all!(:stdio)
     
    -    request = Protobuf.Decoder.decode(bin, Google.Protobuf.Compiler.CodeGeneratorRequest)
    +    request = Protobuf.decode(bin, Google.Protobuf.Compiler.CodeGeneratorRequest)
     
         ctx =
           %Context{}
    
  • grpc_server/mix.exs+3 3 modified
    @@ -33,9 +33,9 @@ defmodule GRPC.Server.MixProject do
     
       defp deps do
         [
    -      # {:grpc_core, path: "../grpc_core"},
    -      {:grpc_core, "~> 1.0.0-rc.1"},
    -      {:protobuf, "~> 0.14"},
    +      {:grpc_core, path: "../grpc_core"},
    +      # {:grpc_core, "~> 1.0.0-rc.1"},
    +      {:protobuf, "~> 0.17"},
           {:cowboy, "~> 2.14"},
           {:cowlib, "~> 2.14"},
           {:flow, "~> 1.2"},
    
  • grpc_server/mix.lock+3 3 modified
    @@ -11,7 +11,7 @@
       "grpc_core": {:hex, :grpc_core, "1.0.0-rc.1", "d82957bca32937bb52df06596cca7550783acc139a06b70202a982ef8b59490e", [:mix], [{:googleapis, "~> 0.1.0", [hex: :googleapis, repo: "hexpm", optional: false]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.14", [hex: :protobuf, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c76233ea374421da562b5b022c22614e81f9cf862da93543cff93c37c085f136"},
       "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
       "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
    -  "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
    +  "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
       "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
       "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
       "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
    @@ -20,9 +20,9 @@
       "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
       "nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"},
       "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
    -  "protobuf": {:hex, :protobuf, "0.15.0", "c9fc1e9fc1682b05c601df536d5ff21877b55e2023e0466a3855cc1273b74dcb", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d7bb325319db1d668838d2691c31c7b793c34111aec87d5ee467a39dac6e051"},
    +  "protobuf": {:hex, :protobuf, "0.17.0", "39e24e43c9648e148feba16ed51100b5b2028ea900b55460377b0476f6e10613", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ca6c91f6f63e2c147b47f03eefd10b80538aa6fc55ff4b12b795efb786b0152f"},
       "protobuf_generate": {:hex, :protobuf_generate, "0.1.3", "57841bc60e2135e190748119d83f78669ee7820c0ad6555ada3cd3cd7df93143", [:mix], [{:protobuf, "~> 0.12", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "dae4139b00ba77a279251a0ceb5593b1bae745e333b4ce1ab7e81e8e4906016b"},
       "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
       "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.3", "4e741024b0b097fe783add06e53ae9a6f23ddc78df1010f215df0c02915ef5a8", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "c23f5f33cb6608542de4d04faf0f0291458c352a4648e4d28d17ee1098cddcc4"},
    -  "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
    +  "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
     }
    
  • grpc_server/test/grpc/codec/erlpack_test.exs+69 0 modified
    @@ -117,4 +117,73 @@ defmodule GRPC.Codec.ErlpackTest do
           assert decoded == term
         end
       end
    +
    +  describe "safe decoding (CVE-2026-48853)" do
    +    test "refuses to create new atoms from untrusted payloads" do
    +      # Craft the external term format for an atom by hand so the atom is never
    +      # created on this node. `:safe` must reject it instead of populating the
    +      # global atom table. Format: <<131, SMALL_ATOM_UTF8_EXT, len, name>>.
    +      name = "erlpack_unknown_atom_payload"
    +      binary = <<131, 119, byte_size(name)::8, name::binary>>
    +
    +      assert_raise ArgumentError, fn ->
    +        Erlpack.decode(binary, AnyModule)
    +      end
    +    end
    +
    +    test "refuses to decode local fun terms (RCE vector)" do
    +      binary = :erlang.term_to_binary(fn -> :exploited end)
    +
    +      assert_raise ArgumentError, fn ->
    +        Erlpack.decode(binary, AnyModule)
    +      end
    +    end
    +
    +    test "refuses to decode external fun terms (RCE vector)" do
    +      binary = :erlang.term_to_binary(&:erlang.system_time/0)
    +
    +      assert_raise ArgumentError, fn ->
    +        Erlpack.decode(binary, AnyModule)
    +      end
    +    end
    +
    +    test "refuses funs nested inside collections" do
    +      for term <- [
    +            {:ok, fn -> :x end},
    +            [1, 2, fn -> :x end],
    +            %{callback: fn -> :x end},
    +            %{nested: %{deep: [fn -> :x end]}}
    +          ] do
    +        binary = :erlang.term_to_binary(term)
    +
    +        assert_raise ArgumentError, fn ->
    +          Erlpack.decode(binary, AnyModule)
    +        end
    +      end
    +    end
    +
    +    test "refuses pids and references" do
    +      for term <- [self(), make_ref(), {:wrapped, self()}] do
    +        binary = :erlang.term_to_binary(term)
    +
    +        assert_raise ArgumentError, fn ->
    +          Erlpack.decode(binary, AnyModule)
    +        end
    +      end
    +    end
    +
    +    test "still decodes payloads referencing existing atoms" do
    +      term = {:ok, :existing_atom, "payload"}
    +      binary = :erlang.term_to_binary(term)
    +
    +      assert Erlpack.decode(binary, AnyModule) == term
    +    end
    +
    +    test "still decodes nested data structures without unsafe terms" do
    +      term = %{list: [1, 2, 3], tuple: {:a, "b"}, nested: %{k: [:ok, "v"]}}
    +      binary = :erlang.term_to_binary(term)
    +
    +      assert Erlpack.decode(binary, AnyModule) == term
    +    end
    +  end
     end
    
  • interop/mix.exs+1 1 modified
    @@ -24,7 +24,7 @@ defmodule Interop.MixProject do
         [
           {:grpc_server, path: "../grpc_server", override: true},
           {:grpc, path: "../grpc", override: true},
    -      {:protobuf, "~> 0.14"},
    +      {:protobuf, "~> 0.17"},
           {:gun, "~> 2.0"},
           {:mint, "~> 1.5"},
           {:grpc_statsd, "~> 0.1.0"},
    
  • interop/mix.lock+3 3 modified
    @@ -9,11 +9,11 @@
       "grpc_statsd": {:hex, :grpc_statsd, "0.1.0", "a95ae388188486043f92a3c5091c143f5a646d6af80c9da5ee616546c4d8f5ff", [:mix], [{:grpc, ">= 0.0.0", [hex: :grpc, repo: "hexpm", optional: true]}, {:statix, ">= 0.0.0", [hex: :statix, repo: "hexpm", optional: true]}], "hexpm", "de0c05db313c7b3ffeff345855d173fd82fec3de16591a126b673f7f698d9e74"},
       "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
       "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
    -  "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
    +  "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
       "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
    -  "protobuf": {:hex, :protobuf, "0.15.0", "c9fc1e9fc1682b05c601df536d5ff21877b55e2023e0466a3855cc1273b74dcb", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5d7bb325319db1d668838d2691c31c7b793c34111aec87d5ee467a39dac6e051"},
    +  "protobuf": {:hex, :protobuf, "0.17.0", "39e24e43c9648e148feba16ed51100b5b2028ea900b55460377b0476f6e10613", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ca6c91f6f63e2c147b47f03eefd10b80538aa6fc55ff4b12b795efb786b0152f"},
       "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
       "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
       "statix": {:hex, :statix, "1.4.0", "c822abd1e60e62828e8460e932515d0717aa3c089b44cc3f795d43b94570b3a8", [:mix], [], "hexpm", "507373cc80925a9b6856cb14ba17f6125552434314f6613c907d295a09d1a375"},
    -  "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
    +  "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
     }
    

Vulnerability mechanics

Root cause

"`GRPC.Codec.Erlpack.decode/2` calls `:erlang.binary_to_term/1` on the raw gRPC message body without the `:safe` option, no size bound, and no type validation, allowing untrusted payloads to mint arbitrary atoms or materialize function terms."

Attack vector

An unauthenticated attacker opens an HTTP/2 connection to a gRPC server that has explicitly registered `GRPC.Codec.Erlpack` in its codecs option [ref_id=2]. The attacker sends a gRPC-framed POST to any RPC path with `Content-Type: application/grpc+erlpack` and a body crafted via `:erlang.term_to_binary/1`. The server's `decode/2` materializes the term without safety checks, enabling two independent paths: atom-table exhaustion (DoS) by encoding fresh atoms, or remote code execution if a decoded fun term reaches a call site that invokes it [ref_id=2].

Affected code

The vulnerability resides in `GRPC.Codec.Erlpack.decode/2` in `lib/grpc/codec/erlpack.ex`, which called `:erlang.binary_to_term/1` on the raw gRPC message body without the `:safe` option, no size bound, and no type guard [ref_id=2]. The patch modifies this function to use `:erlang.binary_to_term(binary, [:safe])` and adds a new `ensure_safe_term!/1` guard that rejects functions, pids, ports, and references [patch_id=6113719].

What the fix does

The patch changes `decode/2` to call `:erlang.binary_to_term(binary, [:safe])`, which prevents the payload from creating new atoms and thus blocks atom-table exhaustion [patch_id=6113719]. It then adds `ensure_safe_term!/1`, a recursive type guard that walks lists, tuples, and maps and raises `ArgumentError` if any element is a function, pid, port, or reference — blocking the RCE vector even on OTP releases where `:safe` alone does not prevent fun materialization [patch_id=6113719]. The moduledoc explicitly notes that atoms referenced by incoming payloads must already exist on the receiving node, which is the case for loaded protobuf structs [patch_id=6113719].

Preconditions

  • configGRPC.Codec.Erlpack must be explicitly registered in the server's codecs option (not enabled by default)
  • networkAttacker must be able to open an HTTP/2 connection to the gRPC server
  • authNo authentication required; any unauthenticated peer can send the crafted payload
  • inputAttacker sends a gRPC-framed POST with Content-Type: application/grpc+erlpack and a crafted binary body

Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.