Skip to content

Security Risk: Stack Exhaustion / DoS via Deeply Nested XML despite DepthTracker #1085

Description

@yoadKochavi

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions