Security Risk: Stack Exhaustion / DoS via Deeply Nested XML despite DepthTracker
Nobody has claimed this yet.
Assessment
This issue has not been assessed yet.
Description
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:
DepthTrackercorrectly 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.
- Dominant language
- C++
- Stars
- 5.8k
- Forks
- 2k
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from leethomason/tinyxml2
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
leethomason/tinyxml2#1097 ·
-
New release requestOpen
leethomason/tinyxml2#1087 · 4 comments ·
-
New relase requestOpen
leethomason/tinyxml2#1084 ·
-
leethomason/tinyxml2#1083 ·
-
Data Corruption via Unrecognized Entity in StrPair::GetStr()Possibly taken @steps-re claimed this 93 days ago. Open
leethomason/tinyxml2#1082 ·
All issues in leethomason/tinyxml2
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
hipRTC lit tests compile against /opt/rocm's LLVM instead of the ROCm under test (ci/ hardcodes LLVM_PATH)Possibly taken @bernardogv claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
請增加教學:數字後的句號Open
Difficulty 1/5 Under an hour Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
Maintainers usually reply within 1 day