Search
Find a vulnerability
Search criteria
4 vulnerabilities by elixir-protobuf
CVE-2026-104635 (GCVE-0-2026-104635)
Vulnerability from nvd – Published: 2026-10-09 08:17 – Updated: 2026-10-09 09:24
VLAI
EPSS
VEX
Title
Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages
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.
Severity
CWE
- CWE-674 - Uncontrolled Recursion
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/elixir-protobuf/protobuf/secur… | relatedvendor-advisory |
| https://cna.erlef.org/cves/CVE-2026-104635.html | related |
| https://osv.dev/vulnerability/EEF-CVE-2026-104635 | related |
| https://github.com/elixir-protobuf/protobuf/commi… | related |
| https://github.com/elixir-protobuf/protobuf/commi… | patch |
Impacted products
2 products
| Vendor | Product | Version | |
|---|---|---|---|
| elixir-protobuf | protobuf |
Affected:
0.8.0 , < 0.17.1
(semver)
cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:* |
|
| elixir-protobuf | protobuf |
Affected:
b0a1d4eaffaf50012fa71a8e931a47cf252d0370 , < e9432ad1c4099511905353cebcececa3a1f7c3ff
(git)
cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:* |
Date Public
2026-10-09 08:17
{
"containers": {
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Protobuf.JSON.Decode\u0027",
"\u0027Elixir.Protobuf.JSON\u0027"
],
"packageName": "protobuf",
"packageURL": "pkg:hex/protobuf",
"product": "protobuf",
"programFiles": [
"lib/protobuf/json/decode.ex",
"lib/protobuf/json.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:from_json_data/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:decode_singular/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode!/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:from_decoded/3"
}
],
"repo": "https://github.com/elixir-protobuf/protobuf",
"vendor": "elixir-protobuf",
"versions": [
{
"lessThan": "0.17.1",
"status": "affected",
"version": "0.8.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Protobuf.JSON.Decode\u0027",
"\u0027Elixir.Protobuf.JSON\u0027"
],
"packageName": "elixir-protobuf/protobuf",
"packageURL": "pkg:github/elixir-protobuf/protobuf",
"product": "protobuf",
"programFiles": [
"lib/protobuf/json/decode.ex",
"lib/protobuf/json.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:from_json_data/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:decode_singular/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode!/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:from_decoded/3"
}
],
"repo": "https://github.com/elixir-protobuf/protobuf",
"vendor": "elixir-protobuf",
"versions": [
{
"lessThan": "e9432ad1c4099511905353cebcececa3a1f7c3ff",
"status": "affected",
"version": "b0a1d4eaffaf50012fa71a8e931a47cf252d0370",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eThe application decodes attacker-controlled JSON with \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e into a message type whose schema contains a self-referential or cyclic embedded message.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*",
"versionEndExcluding": "0.17.1",
"versionStartIncluding": "0.8.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "reporter",
"value": "Daniel Coles"
},
{
"lang": "en",
"type": "finder",
"value": "Daniel Coles"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Andrea Leopardi"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-08T15:40:42.000Z",
"datePublic": "2026-10-09T08:17:52.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUncontrolled Recursion vulnerability in \u003ccode\u003eProtobuf.JSON.Decode\u003c/code\u003e 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 \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e into a schema that contains a self-referential or cyclic message type is affected.\u003c/p\u003e\n\u003cp\u003eIn \u003ccode\u003elib/protobuf/json/decode.ex\u003c/code\u003e, the embedded-message clause of \u003ccode\u003edecode_singular/3\u003c/code\u003e recurses into \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e once per nesting level without incrementing or checking the decoder\u0027s depth counter. The depth guard \u003ccode\u003eincrease_depth_and_maybe_throw/1\u003c/code\u003e covers only the \u003ccode\u003eGoogle.Protobuf.ListValue\u003c/code\u003e and \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e clauses, so the \u003ccode\u003erecursion_limit\u003c/code\u003e 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.\u003c/p\u003e\n\u003cp\u003eThis issue affects protobuf: from 0.8.0 before 0.17.1.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nIn `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\u0027s 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.\n\nThis issue affects protobuf: from 0.8.0 before 0.17.1."
}
],
"value": "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.\n\nIn 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\u0027s 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.\n\nThis issue affects protobuf: from 0.8.0 before 0.17.1."
}
],
"impacts": [
{
"capecId": "CAPEC-230",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 8.2,
"baseSeverity": "HIGH",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-674",
"description": "CWE-674 Uncontrolled Recursion",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-09T09:24:37.025Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-m497-c2h9-rvw6"
},
{
"name": "EEF CNA record for CVE-2026-104635",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-104635.html"
},
{
"name": "OSV record EEF-CVE-2026-104635",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-104635"
},
{
"name": "Introducing commit b0a1d4e in elixir-protobuf/protobuf",
"tags": [
"related"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/b0a1d4eaffaf50012fa71a8e931a47cf252d0370"
},
{
"name": "Fix commit e9432ad in elixir-protobuf/protobuf",
"tags": [
"patch"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/e9432ad1c4099511905353cebcececa3a1f7c3ff"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eReject 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 \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e is called.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eDefine and compile a self-referential proto3 message, for example a \u003ccode\u003eTreeNode\u003c/code\u003e message with a single embedded field \u003ccode\u003echild\u003c/code\u003e of type \u003ccode\u003eTreeNode\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eBuild a JSON document that nests the \u003ccode\u003echild\u003c/code\u003e field several thousand levels deep, for example by wrapping \u003ccode\u003e{}\u003c/code\u003e in \u003ccode\u003e{\"child\": ...}\u003c/code\u003e 5,000 times.\u003c/li\u003e\n\u003cli\u003eDecode it with \u003ccode\u003eProtobuf.JSON.decode(json, TreeNode, recursion_limit: 100)\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eObserve that no \u003ccode\u003eProtobuf.JSON.DecodeError\u003c/code\u003e for an exceeded recursion limit is raised and the decoder walks every level. The same depth in a \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e payload raises \u003ccode\u003e{:recursion_limit_exceeded, 100}\u003c/code\u003e. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Define and compile a self-referential proto3 message, for example a `TreeNode` message with a single embedded field `child` of type `TreeNode`.\n2. Build a JSON document that nests the `child` field several thousand levels deep, for example by wrapping `{}` in `{\"child\": ...}` 5,000 times.\n3. Decode it with `Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100)`.\n4. 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."
}
],
"value": "* Define and compile a self-referential proto3 message, for example a TreeNode message with a single embedded field child of type TreeNode.\n* Build a JSON document that nests the child field several thousand levels deep, for example by wrapping {} in {\"child\": ...} 5,000 times.\n* Decode it with Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100).\n* 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."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Entry points.\u003c/strong\u003e \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, and \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e in \u003ccode\u003elib/protobuf/json.ex\u003c/code\u003e validate options and call \u003ccode\u003eProtobuf.JSON.Decode.from_json_data/3\u003c/code\u003e, which builds a \u003ccode\u003estate\u003c/code\u003e map carrying \u003ccode\u003edepth: 0\u003c/code\u003e and the \u003ccode\u003erecursion_limit\u003c/code\u003e (default 100). \u003ccode\u003efrom_decoded/3\u003c/code\u003e accepts already-parsed data, so the underlying JSON parser never bounds the depth either.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unguarded recursion.\u003c/strong\u003e For a user-defined message module, \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e calls \u003ccode\u003edecode_message/4\u003c/code\u003e, which calls \u003ccode\u003edecode_regular_fields/3\u003c/code\u003e and \u003ccode\u003edecode_oneof_fields/3\u003c/code\u003e. Each field value passes through \u003ccode\u003edecode_value/3\u003c/code\u003e and, for a singular embedded message, reaches the \u003ccode\u003edecode_singular/3\u003c/code\u003e clause matching \u003ccode\u003eembedded?: true\u003c/code\u003e. That clause calls \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e with \u003ccode\u003estate\u003c/code\u003e unchanged, so \u003ccode\u003estate.depth\u003c/code\u003e stays at 0 at every nesting level. Repeated fields and map values reach the same clause through \u003ccode\u003edecode_repeated/3\u003c/code\u003e and \u003ccode\u003edecode_map/3\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Guard coverage.\u003c/strong\u003e \u003ccode\u003eincrease_depth_and_maybe_throw/1\u003c/code\u003e increments \u003ccode\u003edepth\u003c/code\u003e and throws \u003ccode\u003e{:recursion_limit_exceeded, limit}\u003c/code\u003e when it exceeds the limit. It is called only from the \u003ccode\u003eGoogle.Protobuf.ListValue\u003c/code\u003e and \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e clauses, which the \u003ccode\u003eGoogle.Protobuf.Value\u003c/code\u003e clause delegates to. The \u003ccode\u003erecursion_limit\u003c/code\u003e documentation scopes the option to those wrappers, and no other path checks depth.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. Result.\u003c/strong\u003e A self-referential schema such as a \u003ccode\u003eTree\u003c/code\u003e message with a \u003ccode\u003eTree child\u003c/code\u003e 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 \u003ccode\u003emax_heap_size\u003c/code\u003e process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**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.\n\n**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`.\n\n**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.\n\n**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."
}
],
"value": "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.\n\n2. 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.\n\n3. 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.\n\n4. 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."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-104635",
"datePublished": "2026-10-09T08:17:52.372Z",
"dateReserved": "2026-10-08T14:45:01.406Z",
"dateUpdated": "2026-10-09T09:24:37.025Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-54451 (GCVE-0-2026-54451)
Vulnerability from nvd – Published: 2026-09-17 18:19 – Updated: 2026-09-21 21:03
VLAI
EPSS
VEX
Title
Elixir protobuf: Unbounded recursion depth in embedded-message decoding
Summary
Elixir protobuf is a pure Elixir implementation of Google Protobuf. From 0.8.0 until 0.16.1, services that decode attacker-controlled protobuf bytes with Protobuf.Decoder can be taken offline when the schema contains a self-referential or cyclic message type. In lib/protobuf/decoder.ex, Protobuf.Decoder.value_for_field/3 handles an embedded?: true field by recursively entering the decode / build_message / handle_value / value_for_field call chain without enforcing a nesting-depth limit. Deeply nested embedded fields retain non-tail recursive frames, allowing a comparatively small request to consume substantial CPU and memory, pin a BEAM scheduler, and exhaust the node. This issue is fixed in version 0.16.1.
Severity
SSVC
Exploitation: poc
Automatable: yes
Technical Impact: partial
CISA Coordinator · CISA-ADP (v2.0.3)
Decision recorded 2026-09-21 21:03 UTC
CWE
- CWE-674 - Uncontrolled Recursion
Assigner
References
4 references
| URL | Tags |
|---|---|
| https://github.com/elixir-protobuf/protobuf/secur… | x_refsource_CONFIRM |
| https://github.com/elixir-protobuf/protobuf/commi… | x_refsource_MISC |
| https://github.com/elixir-protobuf/protobuf/commi… | x_refsource_MISC |
| https://github.com/elixir-protobuf/protobuf/relea… | x_refsource_MISC |
Impacted products
1 product
| Vendor | Product | Version | |
|---|---|---|---|
| elixir-protobuf | protobuf |
Affected:
>= 0.8.0, < 0.16.1
|
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-54451",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-09-21T21:03:29.889055Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-09-21T21:03:54.370Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-rv48-qqj5-crxg"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"product": "protobuf",
"vendor": "elixir-protobuf",
"versions": [
{
"status": "affected",
"version": "\u003e= 0.8.0, \u003c 0.16.1"
}
]
}
],
"descriptions": [
{
"lang": "en",
"value": "Elixir protobuf is a pure Elixir implementation of Google Protobuf. From 0.8.0 until 0.16.1, services that decode attacker-controlled protobuf bytes with Protobuf.Decoder can be taken offline when the schema contains a self-referential or cyclic message type. In lib/protobuf/decoder.ex, Protobuf.Decoder.value_for_field/3 handles an embedded?: true field by recursively entering the decode / build_message / handle_value / value_for_field call chain without enforcing a nesting-depth limit. Deeply nested embedded fields retain non-tail recursive frames, allowing a comparatively small request to consume substantial CPU and memory, pin a BEAM scheduler, and exhaust the node. This issue is fixed in version 0.16.1."
}
],
"metrics": [
{
"cvssV4_0": {
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 8.2,
"baseSeverity": "HIGH",
"privilegesRequired": "NONE",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE"
}
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-674",
"description": "CWE-674: Uncontrolled Recursion",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-17T18:19:25.334Z",
"orgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"shortName": "GitHub_M"
},
"references": [
{
"name": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-rv48-qqj5-crxg",
"tags": [
"x_refsource_CONFIRM"
],
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-rv48-qqj5-crxg"
},
{
"name": "https://github.com/elixir-protobuf/protobuf/commit/352d1769a0ef575a8d18c3f9620609efdb73e2bc",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/352d1769a0ef575a8d18c3f9620609efdb73e2bc"
},
{
"name": "https://github.com/elixir-protobuf/protobuf/commit/b8efa97790eece3d2d0e8e7c31a45ed409fe5338",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/b8efa97790eece3d2d0e8e7c31a45ed409fe5338"
},
{
"name": "https://github.com/elixir-protobuf/protobuf/releases/tag/v0.16.1",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/elixir-protobuf/protobuf/releases/tag/v0.16.1"
}
],
"source": {
"advisory": "GHSA-rv48-qqj5-crxg",
"discovery": "UNKNOWN"
},
"title": "Elixir protobuf: Unbounded recursion depth in embedded-message decoding"
}
},
"cveMetadata": {
"assignerOrgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"assignerShortName": "GitHub_M",
"cveId": "CVE-2026-54451",
"datePublished": "2026-09-17T18:19:25.334Z",
"dateReserved": "2026-06-15T15:30:40.318Z",
"dateUpdated": "2026-09-21T21:03:54.370Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-104635 (GCVE-0-2026-104635)
Vulnerability from cvelistv5 – Published: 2026-10-09 08:17 – Updated: 2026-10-09 09:24
VLAI
EPSS
VEX
Title
Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages
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.
Severity
CWE
- CWE-674 - Uncontrolled Recursion
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/elixir-protobuf/protobuf/secur… | relatedvendor-advisory |
| https://cna.erlef.org/cves/CVE-2026-104635.html | related |
| https://osv.dev/vulnerability/EEF-CVE-2026-104635 | related |
| https://github.com/elixir-protobuf/protobuf/commi… | related |
| https://github.com/elixir-protobuf/protobuf/commi… | patch |
Impacted products
2 products
| Vendor | Product | Version | |
|---|---|---|---|
| elixir-protobuf | protobuf |
Affected:
0.8.0 , < 0.17.1
(semver)
cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:* |
|
| elixir-protobuf | protobuf |
Affected:
b0a1d4eaffaf50012fa71a8e931a47cf252d0370 , < e9432ad1c4099511905353cebcececa3a1f7c3ff
(git)
cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:* |
Date Public
2026-10-09 08:17
{
"containers": {
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Protobuf.JSON.Decode\u0027",
"\u0027Elixir.Protobuf.JSON\u0027"
],
"packageName": "protobuf",
"packageURL": "pkg:hex/protobuf",
"product": "protobuf",
"programFiles": [
"lib/protobuf/json/decode.ex",
"lib/protobuf/json.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:from_json_data/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:decode_singular/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode!/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:from_decoded/3"
}
],
"repo": "https://github.com/elixir-protobuf/protobuf",
"vendor": "elixir-protobuf",
"versions": [
{
"lessThan": "0.17.1",
"status": "affected",
"version": "0.8.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Protobuf.JSON.Decode\u0027",
"\u0027Elixir.Protobuf.JSON\u0027"
],
"packageName": "elixir-protobuf/protobuf",
"packageURL": "pkg:github/elixir-protobuf/protobuf",
"product": "protobuf",
"programFiles": [
"lib/protobuf/json/decode.ex",
"lib/protobuf/json.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:from_json_data/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON.Decode\u0027:decode_singular/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:decode!/3"
},
{
"name": "\u0027Elixir.Protobuf.JSON\u0027:from_decoded/3"
}
],
"repo": "https://github.com/elixir-protobuf/protobuf",
"vendor": "elixir-protobuf",
"versions": [
{
"lessThan": "e9432ad1c4099511905353cebcececa3a1f7c3ff",
"status": "affected",
"version": "b0a1d4eaffaf50012fa71a8e931a47cf252d0370",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eThe application decodes attacker-controlled JSON with \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e into a message type whose schema contains a self-referential or cyclic embedded message.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:elixir-protobuf:protobuf:*:*:*:*:*:*:*:*",
"versionEndExcluding": "0.17.1",
"versionStartIncluding": "0.8.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "reporter",
"value": "Daniel Coles"
},
{
"lang": "en",
"type": "finder",
"value": "Daniel Coles"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Andrea Leopardi"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-08T15:40:42.000Z",
"datePublic": "2026-10-09T08:17:52.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUncontrolled Recursion vulnerability in \u003ccode\u003eProtobuf.JSON.Decode\u003c/code\u003e 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 \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e into a schema that contains a self-referential or cyclic message type is affected.\u003c/p\u003e\n\u003cp\u003eIn \u003ccode\u003elib/protobuf/json/decode.ex\u003c/code\u003e, the embedded-message clause of \u003ccode\u003edecode_singular/3\u003c/code\u003e recurses into \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e once per nesting level without incrementing or checking the decoder\u0027s depth counter. The depth guard \u003ccode\u003eincrease_depth_and_maybe_throw/1\u003c/code\u003e covers only the \u003ccode\u003eGoogle.Protobuf.ListValue\u003c/code\u003e and \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e clauses, so the \u003ccode\u003erecursion_limit\u003c/code\u003e 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.\u003c/p\u003e\n\u003cp\u003eThis issue affects protobuf: from 0.8.0 before 0.17.1.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nIn `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\u0027s 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.\n\nThis issue affects protobuf: from 0.8.0 before 0.17.1."
}
],
"value": "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.\n\nIn 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\u0027s 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.\n\nThis issue affects protobuf: from 0.8.0 before 0.17.1."
}
],
"impacts": [
{
"capecId": "CAPEC-230",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 8.2,
"baseSeverity": "HIGH",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-674",
"description": "CWE-674 Uncontrolled Recursion",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-09T09:24:37.025Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-m497-c2h9-rvw6"
},
{
"name": "EEF CNA record for CVE-2026-104635",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-104635.html"
},
{
"name": "OSV record EEF-CVE-2026-104635",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-104635"
},
{
"name": "Introducing commit b0a1d4e in elixir-protobuf/protobuf",
"tags": [
"related"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/b0a1d4eaffaf50012fa71a8e931a47cf252d0370"
},
{
"name": "Fix commit e9432ad in elixir-protobuf/protobuf",
"tags": [
"patch"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/e9432ad1c4099511905353cebcececa3a1f7c3ff"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Uncontrolled recursion in elixir-protobuf/protobuf JSON decoding of self-referential messages",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eReject 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 \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, or \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e is called.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eDefine and compile a self-referential proto3 message, for example a \u003ccode\u003eTreeNode\u003c/code\u003e message with a single embedded field \u003ccode\u003echild\u003c/code\u003e of type \u003ccode\u003eTreeNode\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eBuild a JSON document that nests the \u003ccode\u003echild\u003c/code\u003e field several thousand levels deep, for example by wrapping \u003ccode\u003e{}\u003c/code\u003e in \u003ccode\u003e{\"child\": ...}\u003c/code\u003e 5,000 times.\u003c/li\u003e\n\u003cli\u003eDecode it with \u003ccode\u003eProtobuf.JSON.decode(json, TreeNode, recursion_limit: 100)\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eObserve that no \u003ccode\u003eProtobuf.JSON.DecodeError\u003c/code\u003e for an exceeded recursion limit is raised and the decoder walks every level. The same depth in a \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e payload raises \u003ccode\u003e{:recursion_limit_exceeded, 100}\u003c/code\u003e. At larger depths the decoding process consumes hundreds of megabytes and crashes with stack and heap exhaustion.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Define and compile a self-referential proto3 message, for example a `TreeNode` message with a single embedded field `child` of type `TreeNode`.\n2. Build a JSON document that nests the `child` field several thousand levels deep, for example by wrapping `{}` in `{\"child\": ...}` 5,000 times.\n3. Decode it with `Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100)`.\n4. 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."
}
],
"value": "* Define and compile a self-referential proto3 message, for example a TreeNode message with a single embedded field child of type TreeNode.\n* Build a JSON document that nests the child field several thousand levels deep, for example by wrapping {} in {\"child\": ...} 5,000 times.\n* Decode it with Protobuf.JSON.decode(json, TreeNode, recursion_limit: 100).\n* 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."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Entry points.\u003c/strong\u003e \u003ccode\u003eProtobuf.JSON.decode/3\u003c/code\u003e, \u003ccode\u003eProtobuf.JSON.decode!/3\u003c/code\u003e, and \u003ccode\u003eProtobuf.JSON.from_decoded/3\u003c/code\u003e in \u003ccode\u003elib/protobuf/json.ex\u003c/code\u003e validate options and call \u003ccode\u003eProtobuf.JSON.Decode.from_json_data/3\u003c/code\u003e, which builds a \u003ccode\u003estate\u003c/code\u003e map carrying \u003ccode\u003edepth: 0\u003c/code\u003e and the \u003ccode\u003erecursion_limit\u003c/code\u003e (default 100). \u003ccode\u003efrom_decoded/3\u003c/code\u003e accepts already-parsed data, so the underlying JSON parser never bounds the depth either.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Unguarded recursion.\u003c/strong\u003e For a user-defined message module, \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e calls \u003ccode\u003edecode_message/4\u003c/code\u003e, which calls \u003ccode\u003edecode_regular_fields/3\u003c/code\u003e and \u003ccode\u003edecode_oneof_fields/3\u003c/code\u003e. Each field value passes through \u003ccode\u003edecode_value/3\u003c/code\u003e and, for a singular embedded message, reaches the \u003ccode\u003edecode_singular/3\u003c/code\u003e clause matching \u003ccode\u003eembedded?: true\u003c/code\u003e. That clause calls \u003ccode\u003einternal_from_json_data/3\u003c/code\u003e with \u003ccode\u003estate\u003c/code\u003e unchanged, so \u003ccode\u003estate.depth\u003c/code\u003e stays at 0 at every nesting level. Repeated fields and map values reach the same clause through \u003ccode\u003edecode_repeated/3\u003c/code\u003e and \u003ccode\u003edecode_map/3\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Guard coverage.\u003c/strong\u003e \u003ccode\u003eincrease_depth_and_maybe_throw/1\u003c/code\u003e increments \u003ccode\u003edepth\u003c/code\u003e and throws \u003ccode\u003e{:recursion_limit_exceeded, limit}\u003c/code\u003e when it exceeds the limit. It is called only from the \u003ccode\u003eGoogle.Protobuf.ListValue\u003c/code\u003e and \u003ccode\u003eGoogle.Protobuf.Struct\u003c/code\u003e clauses, which the \u003ccode\u003eGoogle.Protobuf.Value\u003c/code\u003e clause delegates to. The \u003ccode\u003erecursion_limit\u003c/code\u003e documentation scopes the option to those wrappers, and no other path checks depth.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. Result.\u003c/strong\u003e A self-referential schema such as a \u003ccode\u003eTree\u003c/code\u003e message with a \u003ccode\u003eTree child\u003c/code\u003e 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 \u003ccode\u003emax_heap_size\u003c/code\u003e process flag defaults to unlimited, so repeated or concurrent requests can exhaust the memory of the whole node.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**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.\n\n**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`.\n\n**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.\n\n**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."
}
],
"value": "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.\n\n2. 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.\n\n3. 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.\n\n4. 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."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-104635",
"datePublished": "2026-10-09T08:17:52.372Z",
"dateReserved": "2026-10-08T14:45:01.406Z",
"dateUpdated": "2026-10-09T09:24:37.025Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
CVE-2026-54451 (GCVE-0-2026-54451)
Vulnerability from cvelistv5 – Published: 2026-09-17 18:19 – Updated: 2026-09-21 21:03
VLAI
EPSS
VEX
Title
Elixir protobuf: Unbounded recursion depth in embedded-message decoding
Summary
Elixir protobuf is a pure Elixir implementation of Google Protobuf. From 0.8.0 until 0.16.1, services that decode attacker-controlled protobuf bytes with Protobuf.Decoder can be taken offline when the schema contains a self-referential or cyclic message type. In lib/protobuf/decoder.ex, Protobuf.Decoder.value_for_field/3 handles an embedded?: true field by recursively entering the decode / build_message / handle_value / value_for_field call chain without enforcing a nesting-depth limit. Deeply nested embedded fields retain non-tail recursive frames, allowing a comparatively small request to consume substantial CPU and memory, pin a BEAM scheduler, and exhaust the node. This issue is fixed in version 0.16.1.
Severity
SSVC
Exploitation: poc
Automatable: yes
Technical Impact: partial
CISA Coordinator · CISA-ADP (v2.0.3)
Decision recorded 2026-09-21 21:03 UTC
CWE
- CWE-674 - Uncontrolled Recursion
Assigner
References
4 references
| URL | Tags |
|---|---|
| https://github.com/elixir-protobuf/protobuf/secur… | x_refsource_CONFIRM |
| https://github.com/elixir-protobuf/protobuf/commi… | x_refsource_MISC |
| https://github.com/elixir-protobuf/protobuf/commi… | x_refsource_MISC |
| https://github.com/elixir-protobuf/protobuf/relea… | x_refsource_MISC |
Impacted products
1 product
| Vendor | Product | Version | |
|---|---|---|---|
| elixir-protobuf | protobuf |
Affected:
>= 0.8.0, < 0.16.1
|
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-54451",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-09-21T21:03:29.889055Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-09-21T21:03:54.370Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-rv48-qqj5-crxg"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"product": "protobuf",
"vendor": "elixir-protobuf",
"versions": [
{
"status": "affected",
"version": "\u003e= 0.8.0, \u003c 0.16.1"
}
]
}
],
"descriptions": [
{
"lang": "en",
"value": "Elixir protobuf is a pure Elixir implementation of Google Protobuf. From 0.8.0 until 0.16.1, services that decode attacker-controlled protobuf bytes with Protobuf.Decoder can be taken offline when the schema contains a self-referential or cyclic message type. In lib/protobuf/decoder.ex, Protobuf.Decoder.value_for_field/3 handles an embedded?: true field by recursively entering the decode / build_message / handle_value / value_for_field call chain without enforcing a nesting-depth limit. Deeply nested embedded fields retain non-tail recursive frames, allowing a comparatively small request to consume substantial CPU and memory, pin a BEAM scheduler, and exhaust the node. This issue is fixed in version 0.16.1."
}
],
"metrics": [
{
"cvssV4_0": {
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 8.2,
"baseSeverity": "HIGH",
"privilegesRequired": "NONE",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE"
}
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-674",
"description": "CWE-674: Uncontrolled Recursion",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-17T18:19:25.334Z",
"orgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"shortName": "GitHub_M"
},
"references": [
{
"name": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-rv48-qqj5-crxg",
"tags": [
"x_refsource_CONFIRM"
],
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-rv48-qqj5-crxg"
},
{
"name": "https://github.com/elixir-protobuf/protobuf/commit/352d1769a0ef575a8d18c3f9620609efdb73e2bc",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/352d1769a0ef575a8d18c3f9620609efdb73e2bc"
},
{
"name": "https://github.com/elixir-protobuf/protobuf/commit/b8efa97790eece3d2d0e8e7c31a45ed409fe5338",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/elixir-protobuf/protobuf/commit/b8efa97790eece3d2d0e8e7c31a45ed409fe5338"
},
{
"name": "https://github.com/elixir-protobuf/protobuf/releases/tag/v0.16.1",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/elixir-protobuf/protobuf/releases/tag/v0.16.1"
}
],
"source": {
"advisory": "GHSA-rv48-qqj5-crxg",
"discovery": "UNKNOWN"
},
"title": "Elixir protobuf: Unbounded recursion depth in embedded-message decoding"
}
},
"cveMetadata": {
"assignerOrgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"assignerShortName": "GitHub_M",
"cveId": "CVE-2026-54451",
"datePublished": "2026-09-17T18:19:25.334Z",
"dateReserved": "2026-06-15T15:30:40.318Z",
"dateUpdated": "2026-09-21T21:03:54.370Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}