Commit e5623ea1e93 for php.net
commit e5623ea1e93030b6628b4374f455bc5a62eeebbb
Author: David Carlier <devnexen@gmail.com>
Date: Tue Jul 7 13:15:55 2026 +0100
ext/dom: use-after-free via DOMNameSpaceNode after DOMDocument::xinclude().
Fix GH-22624
Close GH-22627
diff --git a/NEWS b/NEWS
index 1bba6a7b6c2..9977ea3b340 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,10 @@ PHP NEWS
class constants via OBJ->prop = $val). (Khaled Alam)
. Reverted GH-22833, which attempted to fix bug GH-18985. (ilutov)
+- DOM:
+ . Fixed bug GH-22624 (use-after-free via DOMNameSpaceNode after
+ DOMDocument::xinclude()). (David Carlier)
+
- PDO_PGSQL:
. Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
loop when cleaning up a fetch left in a COPY, a use-after-free when a
diff --git a/ext/dom/node.c b/ext/dom/node.c
index cc063df6696..362000792f1 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -245,11 +245,21 @@ zend_result dom_node_node_type_read(dom_object *obj, zval *retval)
/* }}} */
+static xmlNodePtr dom_node_get_parent(dom_object *obj, xmlNodePtr nodep)
+{
+ if (nodep->type == XML_NAMESPACE_DECL) {
+ dom_object_namespace_node *ns = php_dom_namespace_node_obj_from_obj(&obj->std);
+ return ns->parent_intern ? dom_object_get_node(ns->parent_intern) : NULL;
+ }
+ return nodep->parent;
+}
+
+
static zend_result dom_node_parent_get(dom_object *obj, zval *retval, bool only_element)
{
DOM_PROP_NODE(xmlNodePtr, nodep, obj);
- xmlNodePtr nodeparent = nodep->parent;
+ xmlNodePtr nodeparent = dom_node_get_parent(obj, nodep);
if (!nodeparent || (only_element && nodeparent->type != XML_ELEMENT_NODE)) {
ZVAL_NULL(retval);
return SUCCESS;
@@ -457,7 +467,12 @@ URL: https://dom.spec.whatwg.org/#dom-node-isconnected
zend_result dom_node_is_connected_read(dom_object *obj, zval *retval)
{
DOM_PROP_NODE(xmlNodePtr, nodep, obj);
- ZVAL_BOOL(retval, php_dom_is_node_connected(nodep));
+ if (nodep->type == XML_NAMESPACE_DECL) {
+ xmlNodePtr parent = dom_node_get_parent(obj, nodep);
+ ZVAL_BOOL(retval, parent && php_dom_is_node_connected(parent));
+ } else {
+ ZVAL_BOOL(retval, php_dom_is_node_connected(nodep));
+ }
return SUCCESS;
}
/* }}} */
diff --git a/ext/dom/tests/gh22624.phpt b/ext/dom/tests/gh22624.phpt
new file mode 100644
index 00000000000..cab2ada76cf
--- /dev/null
+++ b/ext/dom/tests/gh22624.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-22624 (Use-after-free via DOMNameSpaceNode after DOMDocument::xinclude())
+--CREDITS--
+ExPatch-LLC
+--EXTENSIONS--
+dom
+--SKIPIF--
+<?php
+if (!function_exists('libxml_set_external_entity_loader')) die('skip xinclude not available');
+?>
+--FILE--
+<?php
+$included = __DIR__ . '/gh22624_included.xml';
+file_put_contents($included, '<?xml version="1.0"?><included/>');
+$href = 'file:///' . ltrim(str_replace('\\', '/', $included), '/');
+
+$doc = new DOMDocument();
+$doc->loadXML('<?xml version="1.0"?>
+<root xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="' . $href . '" xmlns:local="urn:test"/>
+</root>');
+
+$xpath = new DOMXPath($doc);
+$xpath->registerNamespace('xi', 'http://www.w3.org/2001/XInclude');
+$xi = $xpath->query('//xi:include')->item(0);
+$ns = $xpath->query('namespace::local', $xi)->item(0); // DOMNameSpaceNode
+
+$doc->xinclude(); // frees the xi:include element
+
+var_dump($ns->parentNode);
+var_dump($ns->parentElement);
+var_dump($ns->isConnected);
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh22624_included.xml');
+?>
+--EXPECT--
+NULL
+NULL
+bool(false)