Listen to this Post
How the mentioned CVE works
The vulnerability exists in the `Protobuf.Decoder` module of the Hex `protobuf` package for Elixir. The decoder lacks a recursion-depth limit when processing nested, embedded message fields. This oversight affects all versions from 0.8.0 up to, but not including, 0.16.1.
The flawed logic resides in Protobuf.Decoder.value_for_field/3, specifically in the `embedded?: true` branch at lib/protobuf/decoder.ex:218-243. When the decoder encounters an embedded message field, it makes a recursive call to decode(bin, type). This call re-enters the decoding loop through build_message → handle_value → value_for_field. Critically, this recursive call is not in tail position; the result is consumed by the surrounding `decode` after it returns. As a result, every level of nesting retains a live frame on both the process stack and heap.
An attacker can exploit this by crafting a Protobuf message that uses a self-referential schema (e.g., message Tree { Tree child = 1; }) or any cyclic message type. By nesting this embedded field hundreds of thousands to millions of levels deep, the attacker forces the BEAM VM to recurse once per level. Each additional level of nesting in the payload requires only a 1-byte field tag plus a varint length prefix, meaning the depth grows roughly inversely with the payload size. A small request body of just a few kilobytes to a few megabytes can therefore trigger an enormous recursion depth.
The recursion continues until the process exhausts its memory, blows the stack, or starves the scheduler while performing garbage collection over the deeply nested structure. A handful of such concurrent requests can take the entire node offline in a request-amplification denial-of-service attack. This is in stark contrast to reference implementations (Google’s C++, Java, etc.) which cap recursion at 100 levels precisely to prevent this scenario.
The fix, implemented in version 0.16.1, threads a depth counter through the decode functions and raises `Protobuf.DecodeError` once the configurable limit (defaulting to 100) is exceeded.
DailyCVE Form
Platform: Elixir/BEAM
Version: >=0.8.0, <0.16.1
Vulnerability: Unbounded Recursion
Severity: High
Date: 2026-05-12
Prediction: Already Patched (2026-05-12)
What Undercode Say: Analytics
To determine if your application is vulnerable, audit your dependencies for the `protobuf` package.
Check your mix.lock for the protobuf version
grep -A 1 "protobuf" mix.lock
Example output for a vulnerable version
"protobuf": {:hex, :protobuf, "0.15.0", ...}
Example output for a patched version
"protobuf": {:hex, :protobuf, "0.16.1", ...}
You can also check the version directly in your application’s `mix.exs` file.
In mix.exs
defp deps do
[
{:protobuf, "~> 0.15.0"} Vulnerable
{:protobuf, "~> 0.16.1"} Patched
]
end
Exploit: Proof of Concept
The following steps outline how an attacker could craft a malicious payload to trigger the vulnerability.
1. Define a Self-Referential Schema: The attacker defines a Protobuf schema with a self-referential field.
defmodule Tree do use Protobuf, syntax: :proto3 field(:child, 1, type: Tree) end
2. Build the Malicious Payload: The attacker builds a wire-format body from the inside out. At each of `depth` levels, they prepend `<<0x0A, length_varint(inner_size), inner>>` (where tag `0x0A` represents field 1, wire type 2). An iolist with a running byte-size is used to keep generation time linear O(depth).
3. Launch the Attack: The attacker POSTs the body (e.g., a few MB at depth = 1_000_000) as `application/x-protobuf` to any endpoint that calls Tree.decode/1.
4. Observe the Impact: The non-tail `decode(bin, type)` in `value_for_field/3` re-enters once per nesting level, accumulating a frame per level. The decode operation consumes seconds of CPU and hundreds of MB of memory on the victim node; a few concurrent requests are enough to exhaust the node’s resources.
Protection
The primary protection is to upgrade the `protobuf` package to version 0.16.1 or later. This version introduces a recursion limit that defaults to 100, matching the limit in reference implementations.
In mix.exs
defp deps do
[
{:protobuf, "~> 0.16.1"}
]
end
After updating, run `mix deps.get` to fetch the new version.
For applications that cannot be immediately updated, consider these mitigations:
Input Validation: If possible, validate and reject messages that appear to have excessive nesting before passing them to the decoder.
Network Defenses: Deploy a reverse proxy or Web Application Firewall (WAF) that can detect and block requests with unusually deep nesting patterns, although this is a less reliable mitigation.
Resource Limits: Implement strict request size limits and concurrency controls to reduce the impact of a single malicious request.
Impact
- Unauthenticated, network-reachable request-amplification denial of service.
- A single small request can consume seconds of CPU and hundreds of MB of memory on the victim.
- A few concurrent requests can take the entire node offline.
- Any service that decodes attacker-influenced protobuf bytes into a self-referential or cyclic message type is affected.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

