From 660e2c45a0ecafa0384c3fb40749a43143fac98f Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 18 Aug 2026 20:28:09 +0100 Subject: [PATCH] ext/dom: DOMDocument::adoptNode() stale document references in the adopted subtree. Fix #23352 php_dom_transfer_document_ref() only retargeted the leftmost descendant chain, and never an attribute's value nodes, leaving retained nodes pointing at the freed source document. Replaced with an iterative walk over the whole subtree. --- ext/dom/document.c | 27 +++++++++++-------- ...DOMDocument_adoptNode_sibling_subtree.phpt | 22 +++++++++++++++ ext/dom/tests/gh23352.phpt | 21 +++++++++++++++ ext/dom/tests/modern/spec/gh23352.phpt | 21 +++++++++++++++ 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 ext/dom/tests/DOMDocument_adoptNode_sibling_subtree.phpt create mode 100644 ext/dom/tests/gh23352.phpt create mode 100644 ext/dom/tests/modern/spec/gh23352.phpt diff --git a/ext/dom/document.c b/ext/dom/document.c index 9c269e4cb14d..377aee8029cb 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1086,21 +1086,26 @@ static zend_always_inline void php_dom_transfer_document_ref_single_node(xmlNode } } -static void php_dom_transfer_document_ref(xmlNodePtr node, php_libxml_ref_obj *new_document) +static zend_always_inline void php_dom_transfer_document_ref_single_aux(xmlNodePtr node, php_libxml_ref_obj *new_document) { - if (node->children) { - php_dom_transfer_document_ref(node->children, new_document); - } - - while (node) { - if (node->type == XML_ELEMENT_NODE) { - for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) { - php_dom_transfer_document_ref_single_node((xmlNodePtr) attr, new_document); + php_dom_transfer_document_ref_single_node(node, new_document); + if (node->type == XML_ELEMENT_NODE) { + for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { + php_dom_transfer_document_ref_single_node((xmlNodePtr) attr, new_document); + for (xmlNodePtr child = attr->children; child; child = child->next) { + php_dom_transfer_document_ref_single_node((xmlNodePtr) child, new_document); } } + } +} - php_dom_transfer_document_ref_single_node(node, new_document); - node = node->next; +static void php_dom_transfer_document_ref(xmlNodePtr node, php_libxml_ref_obj *new_document) +{ + php_dom_transfer_document_ref_single_aux(node, new_document); + xmlNodePtr tmp = node->children; + while (tmp) { + php_dom_transfer_document_ref_single_aux(tmp, new_document); + tmp = php_dom_next_in_tree_order(tmp, node); } } diff --git a/ext/dom/tests/DOMDocument_adoptNode_sibling_subtree.phpt b/ext/dom/tests/DOMDocument_adoptNode_sibling_subtree.phpt new file mode 100644 index 000000000000..d794324fc899 --- /dev/null +++ b/ext/dom/tests/DOMDocument_adoptNode_sibling_subtree.phpt @@ -0,0 +1,22 @@ +--TEST-- +DOMDocument::adoptNode() with a node retained under a later sibling +--EXTENSIONS-- +dom +--FILE-- +appendChild($source->createElement('root')); +$root->appendChild($source->createElement('first')); +$second = $root->appendChild($source->createElement('second')); +$victim = $second->appendChild($source->createElement('grandchild')); + +$destination = new DOMDocument(); +$destination->appendChild($destination->adoptNode($root)); +unset($destination, $source, $root, $second); + +echo $victim->nodeName, PHP_EOL; + +?> +--EXPECT-- +grandchild diff --git a/ext/dom/tests/gh23352.phpt b/ext/dom/tests/gh23352.phpt new file mode 100644 index 000000000000..67d6d4be2ffb --- /dev/null +++ b/ext/dom/tests/gh23352.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-23352 (UAF reading an attribute value node retained across DOMDocument::adoptNode()) +--EXTENSIONS-- +dom +--FILE-- +appendChild($source->createElement('element')); +$element->setAttribute('attribute', 'victim'); +$victim = $element->getAttributeNode('attribute')->firstChild; + +$destination = new DOMDocument(); +$destination->appendChild($destination->adoptNode($element)); +unset($destination, $source, $element); + +echo $victim->data, PHP_EOL; + +?> +--EXPECT-- +victim diff --git a/ext/dom/tests/modern/spec/gh23352.phpt b/ext/dom/tests/modern/spec/gh23352.phpt new file mode 100644 index 000000000000..f6d72dc9f182 --- /dev/null +++ b/ext/dom/tests/modern/spec/gh23352.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-23352 (UAF reading an attribute value node retained across Dom\Document::adoptNode()) +--EXTENSIONS-- +dom +--FILE-- +'); +$element = $source->documentElement; +$element->setAttribute('attribute', 'victim'); +$victim = $element->getAttributeNode('attribute')->firstChild; + +$destination = Dom\XMLDocument::createEmpty(); +$destination->appendChild($destination->adoptNode($element)); +unset($destination, $source, $element); + +echo $victim->data, PHP_EOL; + +?> +--EXPECT-- +victim