[php-src] master: Fix UAF when setAttributeNS() frees a wrapped attribute child

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-18T14:39:53-04:00

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

Fix UAF when setAttributeNS() frees a wrapped attribute child

dom_set_attribute_ns_modern() handed the element straight to
xmlSetNsProp(), which frees the existing attribute's child list, so a
live Dom\Text wrapper for one of those children was left pointing at
freed memory. Unlink the wrapped children first, as
dom_set_attribute_ns_legacy() already does.

Closes GH-23348

Changed paths:
  A  ext/dom/tests/modern/common/Element_setAttributeNS_live_child.phpt
  M  NEWS
  M  ext/dom/element.c


Diff:

diff --git a/NEWS b/NEWS
index 182c80517b04..d9f2e25bcdf4 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,8 @@ PHP                                                                        NEWS
     DOMDocument::xinclude(). (iliaal)
   . Fixed bug GH-23331 (UAF when node_list_unlink() skips attribute children
     that still have a live wrapper). (iliaal)
+  . Fixed a use-after-free when Dom\Element::setAttributeNS() replaces the
+    value of an attribute whose child still has a live wrapper. (iliaal)
 
 - Intl:
   . Fixed a double-free when IntlGregorianCalendar construction fails after
diff --git a/ext/dom/element.c b/ext/dom/element.c
index 3bce1bdac2a5..8af5d54db338 100644
--- a/ext/dom/element.c
+++ b/ext/dom/element.c
@@ -1030,6 +1030,10 @@ static void dom_set_attribute_ns_modern(dom_object *intern, xmlNodePtr elemp, ze
 	if (errorcode == 0) {
 		php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
 		xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
+		xmlNodePtr existing = (xmlNodePtr) xmlHasNsProp(elemp, localname, ns == NULL ? NULL : ns->href);
+		if (existing != NULL && existing->type != XML_ATTRIBUTE_DECL) {
+			node_list_unlink(existing->children);
+		}
 		xmlAttrPtr attr = xmlSetNsProp(elemp, ns, localname, BAD_CAST value);
 		if (UNEXPECTED(attr == NULL)) {
 			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
diff --git a/ext/dom/tests/modern/common/Element_setAttributeNS_live_child.phpt b/ext/dom/tests/modern/common/Element_setAttributeNS_live_child.phpt
new file mode 100644
index 000000000000..22475c33cf88
--- /dev/null
+++ b/ext/dom/tests/modern/common/Element_setAttributeNS_live_child.phpt
@@ -0,0 +1,37 @@
+--TEST--
+setAttributeNS() keeps an attribute child that still has a live wrapper
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+
+$doc = Dom\XMLDocument::createFromString('<root xmlns:p="urn:x" p:attr="old"/>');
+$el = $doc->documentElement;
+$text = $el->getAttributeNodeNS('urn:x', 'attr')->firstChild;
+$el->setAttributeNS('urn:x', 'p:attr', 'new');
+echo "prefixed, detached: ";
+var_dump($text->parentNode === null);
+echo "prefixed, text: ";
+var_dump($text->textContent);
+echo "prefixed, new value: ";
+var_dump($el->getAttributeNS('urn:x', 'attr'));
+
+$doc = Dom\XMLDocument::createFromString('<root attr="old"/>');
+$el = $doc->documentElement;
+$text = $el->getAttributeNode('attr')->firstChild;
+$el->setAttributeNS(null, 'attr', 'new');
+echo "no namespace, detached: ";
+var_dump($text->parentNode === null);
+echo "no namespace, text: ";
+var_dump($text->textContent);
+echo "no namespace, new value: ";
+var_dump($el->getAttribute('attr'));
+
+?>
+--EXPECT--
+prefixed, detached: bool(true)
+prefixed, text: string(3) "old"
+prefixed, new value: string(3) "new"
+no namespace, detached: bool(true)
+no namespace, text: string(3) "old"
+no namespace, new value: string(3) "new"
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.