Stack Overflow via Unbounded Recursion in XMLNode Destructor
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:
- Programmatic API usage: Applications that build document trees via
InsertEndChild()loops can create trees of arbitrary depth, bypassing the parse-time limit. - 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)recursesAccept()(line 2149/2154):node->Accept(visitor)recurses — this is the code path used byXMLPrinter,SaveFile(), andPrint()
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
-
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.
-
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.
-
Print/Save Crash: Since
Accept()is used byXMLPrinter, callingdoc.Print()ordoc.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
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di leethomason/tinyxml2
-
CDATA output containing ]]> is not well-formed XMLForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
leethomason/tinyxml2#1097 ·
-
New release requestAperta
leethomason/tinyxml2#1087 · 4 commenti ·
-
leethomason/tinyxml2#1085 · 1 commento ·
-
New relase requestAperta
leethomason/tinyxml2#1084 ·
-
Data Corruption via Unrecognized Entity in StrPair::GetStr()Forse già presa @steps-re l’ha presa 94 giorni fa. Aperta
leethomason/tinyxml2#1082 ·
Tutte le issue di leethomason/tinyxml2
Issue simili
-
Component: Python API
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
Vector35/binaryninja-api#8649 ·
I maintainer di solito rispondono entro 3 giorni
-
ai_p2 comp-parquet-reader-v3
Difficoltà 2/5 Mezza giornata Idoneità per principianti 66/100
ClickHouse/ClickHouse#124986 ·
I maintainer di solito rispondono entro 1 giorno
-
bug product: very_good_flutter_plugin
Difficoltà 1/5 1-3 ore Idoneità per principianti 78/100
VeryGoodOpenSource/very_good_templates#654 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
AcademySoftwareFoundation/OpenImageIO#5550 ·
I maintainer di solito rispondono entro 2 giorni