[php-src] master: Merge branch 'PHP-8.4' into PHP-8.5

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-03T13:58:42-04:00

Commit: https://github.com/php/php-src/commit/26fb2a8a117dee1011b7ea075f3fd269871372c8
Raw diff: https://github.com/php/php-src/commit/26fb2a8a117dee1011b7ea075f3fd269871372c8.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix GH-22570: stack overflow serializing a deeply nested Dom\XMLDocument

Changed paths:
  A  ext/dom/tests/modern/xml/gh22570.phpt
  M  NEWS
  M  ext/dom/document.c
  M  ext/dom/inner_outer_html_mixin.c
  M  ext/dom/xml_serializer.c


Diff:

diff --git a/NEWS b/NEWS
index d7a1758a7f2d..94f32f8ca02b 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,10 @@ PHP                                                                        NEWS
 - DBA:
   . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz)
 
+- DOM:
+  . Fixed bug GH-22570 (Stack overflow when serializing a deeply nested
+    Dom\XMLDocument). (iliaal)
+
 - Exif:
   . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
     warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman)
diff --git a/ext/dom/document.c b/ext/dom/document.c
index 66efd70d7704..fc77a9052dde 100644
--- a/ext/dom/document.c
+++ b/ext/dom/document.c
@@ -1674,7 +1674,9 @@ static void dom_document_save_xml(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry
 	}
 
 	if (!res) {
-		php_error_docref(NULL, E_WARNING, "Could not save document");
+		if (!EG(exception)) {
+			php_error_docref(NULL, E_WARNING, "Could not save document");
+		}
 		RETURN_FALSE;
 	} else {
 		RETURN_NEW_STR(res);
diff --git a/ext/dom/inner_outer_html_mixin.c b/ext/dom/inner_outer_html_mixin.c
index e337d5fd3b08..2e080859d694 100644
--- a/ext/dom/inner_outer_html_mixin.c
+++ b/ext/dom/inner_outer_html_mixin.c
@@ -100,7 +100,9 @@ static zend_string *dom_element_html_fragment_serialize(dom_object *obj, xmlNode
 		}
 		if (UNEXPECTED(status < 0)) {
 			smart_str_free_ex(&str, false);
-			php_dom_throw_error_with_message(SYNTAX_ERR, "The resulting XML serialization is not well-formed", true);
+			if (!EG(exception)) {
+				php_dom_throw_error_with_message(SYNTAX_ERR, "The resulting XML serialization is not well-formed", true);
+			}
 			return NULL;
 		}
 		return smart_str_extract(&str);
diff --git a/ext/dom/tests/modern/xml/gh22570.phpt b/ext/dom/tests/modern/xml/gh22570.phpt
new file mode 100644
index 000000000000..af78acf00dfa
--- /dev/null
+++ b/ext/dom/tests/modern/xml/gh22570.phpt
@@ -0,0 +1,40 @@
+--TEST--
+GH-22570 (Stack overflow when serializing a deeply nested Dom\XMLDocument)
+--EXTENSIONS--
+dom
+--SKIPIF--
+<?php
+if (ini_get('zend.max_allowed_stack_size') === false) {
+    die('skip No stack limit support');
+}
+if (getenv('SKIP_ASAN')) {
+    die('skip ASAN needs different stack limit setting due to more stack space usage');
+}
+?>
+--INI--
+zend.max_allowed_stack_size=512K
+--FILE--
+<?php
+// Build via the DOM API, not the parser: libxml caps parse depth even with
+// LIBXML_PARSEHUGE on some platforms; the serializer recursion is the bug.
+$doc = Dom\XMLDocument::createEmpty();
+$node = $doc->appendChild($doc->createElement('root'));
+for ($i = 0; $i < 100000; $i++) {
+    $node = $node->appendChild($doc->createElement('a'));
+}
+
+try {
+    $doc->saveXml();
+} catch (\Error $e) {
+    echo "saveXml: ", $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+    $doc->documentElement->innerHTML;
+} catch (\Error $e) {
+    echo "innerHTML: ", $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+saveXml: Error: Maximum call stack size reached. Infinite recursion?
+innerHTML: Error: Maximum call stack size reached. Infinite recursion?
diff --git a/ext/dom/xml_serializer.c b/ext/dom/xml_serializer.c
index 5e6a6f93b91c..2d9e55a7b706 100644
--- a/ext/dom/xml_serializer.c
+++ b/ext/dom/xml_serializer.c
@@ -1250,6 +1250,15 @@ static int dom_xml_serializing_a_document_node(
 	return 0;
 }
 
+static zend_always_inline bool dom_xml_serialize_check_stack_limit(void)
+{
+#ifdef ZEND_CHECK_STACK_LIMIT
+	return zend_call_stack_overflowed(EG(stack_limit));
+#else
+	return false;
+#endif
+}
+
 /* https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization-algorithm */
 static int dom_xml_serialization_algorithm(
 	dom_xml_serialize_ctx *ctx,
@@ -1261,6 +1270,11 @@ static int dom_xml_serialization_algorithm(
 	bool require_well_formed
 )
 {
+	if (UNEXPECTED(dom_xml_serialize_check_stack_limit())) {
+		zend_throw_error(NULL, "Maximum call stack size reached. Infinite recursion?");
+		return -1;
+	}
+
 	/* If node's interface is: */
 	switch (node->type) {
 		case XML_ELEMENT_NODE:
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.