Vulnerability EEF-CVE-2026-104635

Unknown
UNKNOWN RISK
Vulnerabilities without an assigned CVSS score. Severity is not determinable from available data.
3 hours ago
October 09, 2026 at 08:17 AM UTC
Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages
0.8.0 - 0.17.0
0.8.0 - 0.17.0

Summary

Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages

Details

Summary

Uncontrolled Recursion vulnerability in Protobuf.JSON.Decode in elixir-protobuf protobuf allows an unauthenticated remote attacker to crash the decoding process via a deeply nested JSON document. Any application that decodes attacker-supplied JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a schema that contains a self-referential or cyclic message type is affected.

In lib/protobuf/json/decode.ex, the embedded-message clause of decode_singular/3 recurses into internal_from_json_data/3 once per nesting level without incrementing or checking the decoder's depth counter. The depth guard increase_depth_and_maybe_throw/1 covers only the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, so the recursion_limit option has no effect on user-defined message types. Each nesting level allocates a stack frame and heap objects, and a sufficiently deep document exhausts the memory of the decoding process. Confidentiality and integrity are not affected.

This issue affects protobuf: from 0.8.0 before 0.17.1.

Details

1. Entry points. Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, and Protobuf.JSON.from_decoded/3 in lib/protobuf/json.ex validate options and call Protobuf.JSON.Decode.from_json_data/3, which builds a state map carrying depth: 0 and the recursion_limit (default 100). from_decoded/3 accepts already-parsed data, so the underlying JSON parser never bounds the depth either.

2. Unguarded recursion. For a user-defined message module, internal_from_json_data/3 calls decode_message/4, which calls decode_regular_fields/3 and decode_oneof_fields/3. Each field value passes through decode_value/3 and, for a singular embedded message, reaches the decode_singular/3 clause matching embedded?: true. That clause calls internal_from_json_data/3 with state unchanged, so state.depth stays at 0 at every nesting level. Repeated fields and map values reach the same clause through decode_repeated/3 and decode_map/3.

3. Guard coverage. increase_depth_and_maybe_throw/1 increments depth and throws {:recursion_limit_exceeded, limit} when it exceeds the limit. It is called only from the Google.Protobuf.ListValue and Google.Protobuf.Struct clauses, which the Google.Protobuf.Value clause delegates to. The recursion_limit documentation scopes the option to those wrappers, and no other path checks depth.

4. Result. A self-referential schema such as a Tree message with a Tree child field recurses once per JSON nesting level with no bound. Each level holds a live stack frame and allocates heap objects, so a sufficiently deep document exhausts the memory of the decoding process and crashes it. The BEAM max_heap_size process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node.

Proof of concept

  1. Define and compile a self-referential proto3 message, for example a TreeNode message with a single embedded field child of type TreeNode.
  2. Build a JSON document that nests the child field several thousand levels deep, for example by wrapping {} in {"child": ...} 5,000 times.
  3. Decode it with Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100).
  4. Observe that no Protobuf.JSON.DecodeError for an exceeded recursion limit is raised and the decoder walks every level. The same depth in a Google.Protobuf.Struct payload raises {:recursion_limit_exceeded, 100}. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion.

Impact

An unauthenticated client that can reach an endpoint decoding JSON into a self-referential message type can crash the decoding process through stack and memory exhaustion with a single request. Repeated or concurrent requests can exhaust the memory of the whole node and disrupt co-located workloads.

Workarounds

Reject JSON documents whose nesting depth exceeds a fixed bound at the web or middleware layer before passing them to the decoder. No decoder option limits recursion for user-defined message types, so the check must happen before Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 is called.

Configurations

The application decodes attacker-controlled JSON with Protobuf.JSON.decode/3, Protobuf.JSON.decode!/3, or Protobuf.JSON.from_decoded/3 into a message type whose schema contains a self-referential or cyclic embedded message.

Impacted packages

Timeline

Published
3 hours ago
October 09, 2026 at 08:17 AM UTC
Fixed (0.17.1)
4 hours ago
October 09, 2026 at 07:39 AM UTC
Last Modified
2 hours ago
October 09, 2026 at 09:30 AM UTC