[php-src] PHP-8.4: Fix GH-23120: DOMNode::isEqualNode stack overflow on deeply nested trees (#23140)

Weilin Du via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-08-09T00:04:06+08:00

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

Fix GH-23120: DOMNode::isEqualNode stack overflow on deeply nested trees (#23140)

Changed paths:
  A  ext/dom/tests/gh23120.phpt
  M  NEWS
  M  ext/dom/node.c


Diff:

diff --git a/NEWS b/NEWS
index 81f448cbd56e..e4a21b1bfa44 100644
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,8 @@ 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-23120 (Stack overflow when comparing deeply nested DOM nodes
+    with DOMNode::isEqualNode()). (Weilin Du)
 
 - MBString:
   . Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative
diff --git a/ext/dom/node.c b/ext/dom/node.c
index 81c80cb0c8ac..a42dfedc32a5 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -1730,11 +1730,27 @@ static bool php_dom_is_equal_attr(const xmlAttr *this_attr, const xmlAttr *other
 		&& php_dom_node_is_content_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr);
 }
 
+static zend_always_inline bool php_dom_node_is_equal_node_check_stack_limit(void)
+{
+#ifdef ZEND_CHECK_STACK_LIMIT
+	return zend_call_stack_overflowed(EG(stack_limit));
+#else
+	return false;
+#endif
+}
+
 static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other, bool spec_compliant)
 {
 	ZEND_ASSERT(this != NULL);
 	ZEND_ASSERT(other != NULL);
 
+	if (UNEXPECTED(php_dom_node_is_equal_node_check_stack_limit())) {
+		if (!EG(exception)) {
+			zend_throw_error(NULL, "Maximum call stack size reached.");
+		}
+		return false;
+	}
+
 	if (this->type != other->type) {
 		return false;
 	}
@@ -1795,6 +1811,7 @@ static void dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAMETERS, bool mod
 	zval *id, *node;
 	xmlNodePtr otherp, nodep;
 	dom_object *intern;
+	bool result;
 
 	id = ZEND_THIS;
 	ZEND_PARSE_PARAMETERS_START(1, 1)
@@ -1817,7 +1834,11 @@ static void dom_node_is_equal_node_common(INTERNAL_FUNCTION_PARAMETERS, bool mod
 		RETURN_BOOL(nodep == NULL && otherp == NULL);
 	}
 
-	RETURN_BOOL(php_dom_node_is_equal_node(nodep, otherp, modern));
+	result = php_dom_node_is_equal_node(nodep, otherp, modern);
+	if (UNEXPECTED(EG(exception))) {
+		RETURN_THROWS();
+	}
+	RETURN_BOOL(result);
 }
 
 PHP_METHOD(DOMNode, isEqualNode)
diff --git a/ext/dom/tests/gh23120.phpt b/ext/dom/tests/gh23120.phpt
new file mode 100644
index 000000000000..511314d9cbba
--- /dev/null
+++ b/ext/dom/tests/gh23120.phpt
@@ -0,0 +1,42 @@
+--TEST--
+GH-23120 (Stack overflow when comparing deeply nested DOM nodes)
+--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
+function create_deep_document(): DOMDocument {
+    $doc = new DOMDocument();
+    $node = $doc->createElement('leaf', 'x');
+
+    for ($i = 0; $i < 10000; $i++) {
+        $parent = $doc->createElement('a');
+        $parent->appendChild($node);
+        $node = $parent;
+    }
+
+    $doc->appendChild($node);
+    return $doc;
+}
+
+$doc1 = create_deep_document();
+$doc2 = create_deep_document();
+
+try {
+    var_dump($doc1->documentElement->isEqualNode($doc2->documentElement));
+} catch (\Error $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Error: Maximum call stack size reached.
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.