Security Advisory: Stack Exhaustion via Uncontrolled Recursion in TinyXML-2
Description
A stack exhaustion vulnerability has been identified in TinyXML-2 during the parsing of deeply nested XML documents.
Although the library implements a defensive mechanism via tinyxml2::XMLDocument::DepthTracker to restrict recursion depth within XMLNode::ParseDeep(), the recursive call chain between XMLNode::ParseDeep() and XMLElement::ParseDeep() can still exhaust the available thread stack memory before reaching the hardcoded logical limit.
This results in a native stack overflow, causing immediate abnormal application termination and leading to a reliable Denial of Service (DoS).
This architectural limitation is particularly impactful when the binary is deployed in resource-constrained environments or compiled with memory instrumentation tools such as AddressSanitizer (ASan), where increased stack frame sizes accelerate stack exhaustion.
Impact
| Property |
Value |
| Vulnerability Type |
CWE-674 (Uncontrolled Recursion), CWE-121 (Stack-based Buffer Overflow) |
| Impact |
Denial of Service (DoS) |
| Result |
Process crash / Stack Overflow |
| Severity |
Medium–High (environment dependent) |
Technical Details
The XML parser recursively processes nested elements through the following call chain:
XMLNode::ParseDeep()
↓
XMLElement::ParseDeep()
↓
XMLNode::ParseDeep()
↓
...
TinyXML-2 uses XMLDocument::DepthTracker to count recursion depth.
However, this mechanism only limits the logical recursion depth and does not account for the amount of native stack already consumed.
Consequently, on systems where the available thread stack is smaller than the configured logical recursion limit, the process crashes before DepthTracker has an opportunity to return a graceful parser error.
This makes the current protection configuration-dependent rather than architecture-aware.
Proof of Concept (PoC)
1. Generate a Malicious XML Document
# generate_poc.py
depth = 500000
malicious_xml = "<a>" * depth
with open("poc.xml", "w") as f:
f.write(malicious_xml)
2. Compile the Test Harness
g++ -o test_stack test_stack.cpp tinyxml2.cpp
3. Simulate a Constrained Runtime Environment
ulimit -s 64
./test_stack poc.xml
Expected result:
Segmentation fault (core dumped)
The vulnerability is reproducible on an unmodified TinyXML-2 build without changing any source code constants.
AddressSanitizer Verification
When compiled with AddressSanitizer, the increased stack frame sizes cause the issue to manifest immediately.
Example output:
==2480791==ERROR: AddressSanitizer: stack-overflow on address 0x7ffffbffef08
(pc 0x7ffff78e4e00 bp 0x7ffffbfff780 sp 0x7ffffbffef10 T0)
#0 strncmp(...)
#1 tinyxml2::XMLUtil::StringEqual(...)
#2 tinyxml2::XMLDocument::Identify(...)
#3 tinyxml2::XMLNode::ParseDeep(...)
#4 tinyxml2::XMLElement::ParseDeep(...)
#5 tinyxml2::XMLNode::ParseDeep(...)
The alternating recursive calls continue until the thread stack is exhausted.
Technical Analysis
To evaluate the effectiveness of the built-in mitigation, the value of TINYXML2_MAX_ELEMENT_DEPTH inside tinyxml2.h was modified during testing.
The experiments showed:
DepthTracker correctly enforces its configured recursion limit.
- Increasing the limit proportionally increases recursion depth.
- Under constrained stack environments, the application crashes before the configured limit is reached.
- The failure occurs as a native stack overflow rather than a graceful parser error.
These observations indicate that the current mitigation acts solely as a logical depth limiter and is not capable of preventing stack exhaustion caused by runtime stack constraints.
Root Cause
The parser relies entirely on recursive descent parsing.
Its maximum safe nesting depth therefore depends on:
- Operating system stack limits
- Thread stack size
- Compiler optimizations
- Runtime instrumentation (ASan, UBSan, etc.)
- Platform-specific calling conventions
As a result, the parser cannot guarantee graceful failure across different execution environments.
Security Impact
An attacker can craft a sufficiently deep XML document that reliably terminates any application using TinyXML-2 to parse untrusted XML.
The attack:
- requires no special privileges,
- requires no malformed memory writes,
- requires no race conditions,
- is deterministic,
- results in a reliable Denial of Service (DoS).
Proposed Mitigations
Option 1 — Runtime Stack Awareness
Introduce platform-aware stack probing or conservative stack budgeting before entering deeper recursive calls.
If the remaining stack becomes insufficient, terminate parsing gracefully with an error instead of allowing a native stack overflow.
Option 2 — Iterative Parsing (Recommended)
Replace the recursive descent implementation inside ParseDeep() with an iterative parser using an explicit heap-allocated stack.
Benefits include:
- Eliminates dependence on native thread stack size.
- Prevents stack exhaustion regardless of XML nesting depth.
- Provides consistent behavior across operating systems and runtime environments.
- Scales with available heap memory instead of fixed thread stack limits.
Reproduction Environment
- TinyXML-2 (current master)
- Linux
- GCC / Clang
- AddressSanitizer (optional)
- Default library source code
- Reduced stack size via
ulimit -s
Conclusion
While XMLDocument::DepthTracker successfully enforces a configurable recursion limit, it does not protect against native stack exhaustion when runtime stack limits are reached first.
Consequently, deeply nested XML documents can still trigger a deterministic stack overflow and application crash under constrained environments, making the current mitigation insufficient as a complete architectural safeguard.
Security Advisory: Stack Exhaustion via Uncontrolled Recursion in TinyXML-2
Description
A stack exhaustion vulnerability has been identified in TinyXML-2 during the parsing of deeply nested XML documents.
Although the library implements a defensive mechanism via
tinyxml2::XMLDocument::DepthTrackerto restrict recursion depth withinXMLNode::ParseDeep(), the recursive call chain betweenXMLNode::ParseDeep()andXMLElement::ParseDeep()can still exhaust the available thread stack memory before reaching the hardcoded logical limit.This results in a native stack overflow, causing immediate abnormal application termination and leading to a reliable Denial of Service (DoS).
This architectural limitation is particularly impactful when the binary is deployed in resource-constrained environments or compiled with memory instrumentation tools such as AddressSanitizer (ASan), where increased stack frame sizes accelerate stack exhaustion.
Impact
Technical Details
The XML parser recursively processes nested elements through the following call chain:
TinyXML-2 uses
XMLDocument::DepthTrackerto count recursion depth.However, this mechanism only limits the logical recursion depth and does not account for the amount of native stack already consumed.
Consequently, on systems where the available thread stack is smaller than the configured logical recursion limit, the process crashes before
DepthTrackerhas an opportunity to return a graceful parser error.This makes the current protection configuration-dependent rather than architecture-aware.
Proof of Concept (PoC)
1. Generate a Malicious XML Document
2. Compile the Test Harness
3. Simulate a Constrained Runtime Environment
ulimit -s 64 ./test_stack poc.xmlExpected result:
The vulnerability is reproducible on an unmodified TinyXML-2 build without changing any source code constants.
AddressSanitizer Verification
When compiled with AddressSanitizer, the increased stack frame sizes cause the issue to manifest immediately.
Example output:
The alternating recursive calls continue until the thread stack is exhausted.
Technical Analysis
To evaluate the effectiveness of the built-in mitigation, the value of
TINYXML2_MAX_ELEMENT_DEPTHinsidetinyxml2.hwas modified during testing.The experiments showed:
DepthTrackercorrectly enforces its configured recursion limit.These observations indicate that the current mitigation acts solely as a logical depth limiter and is not capable of preventing stack exhaustion caused by runtime stack constraints.
Root Cause
The parser relies entirely on recursive descent parsing.
Its maximum safe nesting depth therefore depends on:
As a result, the parser cannot guarantee graceful failure across different execution environments.
Security Impact
An attacker can craft a sufficiently deep XML document that reliably terminates any application using TinyXML-2 to parse untrusted XML.
The attack:
Proposed Mitigations
Option 1 — Runtime Stack Awareness
Introduce platform-aware stack probing or conservative stack budgeting before entering deeper recursive calls.
If the remaining stack becomes insufficient, terminate parsing gracefully with an error instead of allowing a native stack overflow.
Option 2 — Iterative Parsing (Recommended)
Replace the recursive descent implementation inside
ParseDeep()with an iterative parser using an explicit heap-allocated stack.Benefits include:
Reproduction Environment
ulimit -sConclusion
While
XMLDocument::DepthTrackersuccessfully enforces a configurable recursion limit, it does not protect against native stack exhaustion when runtime stack limits are reached first.Consequently, deeply nested XML documents can still trigger a deterministic stack overflow and application crash under constrained environments, making the current mitigation insufficient as a complete architectural safeguard.