From 3d2f86c2c2b68f76b85299b525ed094d41c9d2df Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sat, 8 Aug 2026 16:16:41 +0800 Subject: [PATCH 1/2] ext/dom: Do not recursively normalize descendant elements --- NEWS | 4 ++++ ext/dom/php_dom.c | 11 ++++------ ext/dom/tests/gh23116.phpt | 29 +++++++++++++++++++++++++++ ext/dom/tests/modern/xml/gh23117.phpt | 29 +++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 ext/dom/tests/gh23116.phpt create mode 100644 ext/dom/tests/modern/xml/gh23117.phpt diff --git a/NEWS b/NEWS index ba832ef05888..ececa76d4eb2 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,10 @@ PHP NEWS . Fixed bug GH-22447 (UAF at dom_objects_free_storage when setting an attribute node that collides by local name with a namespaced attribute). (David Carlier) + . Fixed bug GH-23116 (Stack overflow when normalizing a deeply nested + DOMDocument). (Weilin Du) + . Fixed bug GH-23117 (Stack overflow when normalizing a deeply nested + Dom\XMLDocument). (Weilin Du) - MBString: . Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 135b3cdc5caa..560ef65e6b00 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1975,7 +1975,7 @@ void php_dom_normalize_legacy(xmlNodePtr nodep) case XML_TEXT_NODE: dom_merge_adjacent_exclusive_text_nodes(child); if (is_empty_node(child)) { - xmlNodePtr nextp = child->next; + xmlNodePtr nextp = php_dom_next_in_tree_order(child, nodep); xmlUnlinkNode(child); free_node(child); child = nextp; @@ -1983,7 +1983,6 @@ void php_dom_normalize_legacy(xmlNodePtr nodep) } break; case XML_ELEMENT_NODE: - php_dom_normalize_legacy(child); xmlAttrPtr attr = child->properties; while (attr != NULL) { php_dom_normalize_legacy((xmlNodePtr) attr); @@ -1993,7 +1992,7 @@ void php_dom_normalize_legacy(xmlNodePtr nodep) default: break; } - child = child->next; + child = php_dom_next_in_tree_order(child, nodep); } } /* }}} end php_dom_normalize_legacy */ @@ -2011,7 +2010,7 @@ void php_dom_normalize_modern(xmlNodePtr this) /* 2. If length is zero, then remove node and continue with the next exclusive Text node, if any. */ if (is_empty) { - xmlNodePtr next = node->next; + xmlNodePtr next = php_dom_next_in_tree_order(node, this); xmlUnlinkNode(node); free_node(node); node = next; @@ -2025,10 +2024,8 @@ void php_dom_normalize_modern(xmlNodePtr this) dom_merge_adjacent_exclusive_text_nodes(node); /* Steps 5-6 deal with mutation records, we don't do that here. */ - } else if (node->type == XML_ELEMENT_NODE) { - php_dom_normalize_modern(node); } - node = node->next; + node = php_dom_next_in_tree_order(node, this); } } diff --git a/ext/dom/tests/gh23116.phpt b/ext/dom/tests/gh23116.phpt new file mode 100644 index 000000000000..80290d146881 --- /dev/null +++ b/ext/dom/tests/gh23116.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-23116 (Stack overflow when normalizing a deeply nested DOMDocument) +--EXTENSIONS-- +dom +--FILE-- +createElement('leaf'); +$node->appendChild($doc->createTextNode('x')); +$node->appendChild($doc->createTextNode('')); +$node->appendChild($doc->createTextNode('y')); +$leaf = $node; + +for ($i = 0; $i < 100000; $i++) { + $parent = $doc->createElement('a'); + $parent->appendChild($node); + $node = $parent; +} + +$doc->appendChild($node); +$doc->normalize(); + +var_dump($leaf->childNodes->length); +var_dump($leaf->textContent); +?> +--EXPECT-- +int(1) +string(2) "xy" diff --git a/ext/dom/tests/modern/xml/gh23117.phpt b/ext/dom/tests/modern/xml/gh23117.phpt new file mode 100644 index 000000000000..f70097843df4 --- /dev/null +++ b/ext/dom/tests/modern/xml/gh23117.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-23117 (Stack overflow when normalizing a deeply nested Dom\XMLDocument) +--EXTENSIONS-- +dom +--FILE-- +createElement('leaf'); +$node->appendChild($doc->createTextNode('x')); +$node->appendChild($doc->createTextNode('')); +$node->appendChild($doc->createTextNode('y')); +$leaf = $node; + +for ($i = 0; $i < 100000; $i++) { + $parent = $doc->createElement('a'); + $parent->appendChild($node); + $node = $parent; +} + +$doc->appendChild($node); +$doc->normalize(); + +var_dump($leaf->childNodes->length); +var_dump($leaf->textContent); +?> +--EXPECT-- +int(1) +string(2) "xy" From 82418e92b247537df9074cec1b755f8686db05c7 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sat, 8 Aug 2026 16:28:19 +0800 Subject: [PATCH 2/2] fix build for C23 --- ext/dom/php_dom.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 560ef65e6b00..2cf97014896b 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1982,13 +1982,14 @@ void php_dom_normalize_legacy(xmlNodePtr nodep) continue; } break; - case XML_ELEMENT_NODE: + case XML_ELEMENT_NODE: { xmlAttrPtr attr = child->properties; while (attr != NULL) { php_dom_normalize_legacy((xmlNodePtr) attr); attr = attr->next; } break; + } default: break; }