diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c
index 622dd187dd84..349c304c9f25 100644
--- a/ext/dom/xpath_callbacks.c
+++ b/ext/dom/xpath_callbacks.c
@@ -347,15 +347,15 @@ static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, ui
xmlNodePtr node = obj->nodesetval->nodeTab[j];
zval child;
if (UNEXPECTED(node->type == XML_NAMESPACE_DECL)) {
- xmlNodePtr nsparent = node->_private;
xmlNsPtr original = (xmlNsPtr) node;
/* Make sure parent dom object exists, so we can take an extra reference. */
zval parent_zval; /* don't destroy me, my lifetime is transferred to the fake namespace decl */
- php_dom_create_object(nsparent, &parent_zval, intern);
+ proxy_factory(node->_private, &parent_zval, intern, ctxt);
dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval);
+ xmlNodePtr parent = dom_object_get_node(parent_intern);
- php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
+ php_dom_create_fake_namespace_decl(parent, original, &child, parent_intern);
} else {
proxy_factory(node, &child, intern, ctxt);
}
diff --git a/ext/xsl/tests/gh22621.phpt b/ext/xsl/tests/gh22621.phpt
new file mode 100644
index 000000000000..b31671eff630
--- /dev/null
+++ b/ext/xsl/tests/gh22621.phpt
@@ -0,0 +1,63 @@
+--TEST--
+GH-22621 (UAF via XSLTProcessor::registerPHPFunctions() retaining namespace nodes from an external document)
+--EXTENSIONS--
+dom
+xsl
+--CREDITS--
+ExPatch-LLC
+--FILE--
+secret');
+$uri = 'file:///' . ltrim(str_replace('\\', '/', $cDIR), '/') . '/gh22621_external.xml';
+
+$stored_ns = null;
+
+function capture_ns($nodes) {
+ global $stored_ns;
+ foreach ($nodes as $node) {
+ if ($node instanceof DOMNameSpaceNode) {
+ $stored_ns = $node;
+ }
+ }
+ return "captured";
+}
+
+$xsl = new DOMDocument();
+$xsl->loadXML(<<
+
+
+
+
+
+
+
+XSL);
+
+$doc = new DOMDocument();
+$doc->loadXML('');
+
+$proc = new XSLTProcessor();
+$proc->registerPHPFunctions();
+$proc->importStylesheet($xsl);
+echo $proc->transformToXml($doc);
+
+var_dump($stored_ns->localName);
+var_dump($stored_ns->namespaceURI);
+var_dump($stored_ns->parentNode->localName);
+var_dump($stored_ns->ownerDocument instanceof DOMDocument);
+?>
+--CLEAN--
+
+--EXPECTF--
+
+captured
+string(6) "custom"
+string(10) "urn:custom"
+string(4) "root"
+bool(true)