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 | CPE status | |
|---|---|---|---|---|
| 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",
"vulnerability-lookup:meta": {
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"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"
}
]
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"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."
}
],
"id": "CVE-2026-104635",
"lastModified": "2026-10-09T10:16:37.143",
"metrics": {
"cvssMetricV40": [
{
"cvssData": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"availabilityRequirement": "NOT_DEFINED",
"baseScore": 8.2,
"baseSeverity": "HIGH",
"confidentialityRequirement": "NOT_DEFINED",
"exploitMaturity": "NOT_DEFINED",
"integrityRequirement": "NOT_DEFINED",
"modifiedAttackComplexity": "NOT_DEFINED",
"modifiedAttackRequirements": "NOT_DEFINED",
"modifiedAttackVector": "NOT_DEFINED",
"modifiedPrivilegesRequired": "NOT_DEFINED",
"modifiedSubAvailabilityImpact": "NOT_DEFINED",
"modifiedSubConfidentialityImpact": "NOT_DEFINED",
"modifiedSubIntegrityImpact": "NOT_DEFINED",
"modifiedUserInteraction": "NOT_DEFINED",
"modifiedVulnAvailabilityImpact": "NOT_DEFINED",
"modifiedVulnConfidentialityImpact": "NOT_DEFINED",
"modifiedVulnIntegrityImpact": "NOT_DEFINED",
"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
},
"published": "2026-10-09T09:17:07.360",
"references": [
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://cna.erlef.org/cves/CVE-2026-104635.html"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-protobuf/protobuf/commit/b0a1d4eaffaf50012fa71a8e931a47cf252d0370"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-protobuf/protobuf/commit/e9432ad1c4099511905353cebcececa3a1f7c3ff"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-protobuf/protobuf/security/advisories/GHSA-m497-c2h9-rvw6"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-104635"
}
],
"sourceIdentifier": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"vulnStatus": "Received",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-674"
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
}
},
"redhat_vex": {
"aggregate_severity": "Moderate",
"current_release_date": "2026-10-09T11:05:21+00:00",
"cve": "CVE-2026-104635",
"id": "CVE-2026-104635",
"initial_release_date": "2026-10-09T08:17:52.372000+00:00",
"product_status:known_affected": "46",
"product_status:known_not_affected": "1",
"source": "Red Hat CSAF VEX",
"status": "final",
"title": "protobuf: protobuf: Denial of Service via deeply nested JSON input",
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-104635.json",
"version": "3"
}
}
}
Loading…
Loading…
Experimental. This forecast is provided for visualization only and may change without notice. Do not use it for operational decisions.
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Loading…
Loading…
The MITRE ATT&CK techniques below are AI-generated suggestions, inferred from the description of the
vulnerability by the CIRCL/vulnerability-attack-technique-classification-roberta-base
model, served locally by ML-Gateway.
They have not been verified by an analyst and are provided for guidance only.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Loading…
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.
Loading…