Skip to content

Commit 5646223

Browse files
committed
ext/dom: Use-after-free with namespace nodes from XSLTProcessor::registerPHPFunctions().
Fix #22621 Namespace parents from an external document() were wrapped directly instead of through the proxy factory, so they skipped the copy that keeps non-main-document nodes alive. A DOMNameSpaceNode retained by userland then dangled once xsltFreeTransformContext() freed the external document. Route the namespace parent through the proxy factory too.
1 parent 3704518 commit 5646223

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

ext/dom/xpath_callbacks.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,15 +347,15 @@ static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, ui
347347
xmlNodePtr node = obj->nodesetval->nodeTab[j];
348348
zval child;
349349
if (UNEXPECTED(node->type == XML_NAMESPACE_DECL)) {
350-
xmlNodePtr nsparent = node->_private;
351350
xmlNsPtr original = (xmlNsPtr) node;
352351

353352
/* Make sure parent dom object exists, so we can take an extra reference. */
354353
zval parent_zval; /* don't destroy me, my lifetime is transferred to the fake namespace decl */
355-
php_dom_create_object(nsparent, &parent_zval, intern);
354+
proxy_factory(node->_private, &parent_zval, intern, ctxt);
356355
dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval);
356+
xmlNodePtr parent = dom_object_get_node(parent_intern);
357357

358-
php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
358+
php_dom_create_fake_namespace_decl(parent, original, &child, parent_intern);
359359
} else {
360360
proxy_factory(node, &child, intern, ctxt);
361361
}

ext/xsl/tests/gh22621.phpt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
GH-22621 (UAF via XSLTProcessor::registerPHPFunctions() retaining namespace nodes from an external document)
3+
--EXTENSIONS--
4+
dom
5+
xsl
6+
--CREDITS--
7+
ExPatch-LLC
8+
--FILE--
9+
<?php
10+
$cDIR = __DIR__;
11+
file_put_contents($cDIR . '/gh22621_external.xml', '<root xmlns:custom="urn:custom"><data>secret</data></root>');
12+
13+
$stored_ns = null;
14+
15+
function capture_ns($nodes) {
16+
global $stored_ns;
17+
foreach ($nodes as $node) {
18+
if ($node instanceof DOMNameSpaceNode) {
19+
$stored_ns = $node;
20+
}
21+
}
22+
return "captured";
23+
}
24+
25+
$xsl = new DOMDocument();
26+
$xsl->loadXML(<<<XSL
27+
<?xml version="1.0"?>
28+
<xsl:stylesheet version="1.0"
29+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
30+
xmlns:php="http://php.net/xsl">
31+
<xsl:template match="/">
32+
<result>
33+
<xsl:value-of select="php:function('capture_ns', document('$cDIR/gh22621_external.xml')/*/namespace::*)"/>
34+
</result>
35+
</xsl:template>
36+
</xsl:stylesheet>
37+
XSL);
38+
39+
$doc = new DOMDocument();
40+
$doc->loadXML('<input/>');
41+
42+
$proc = new XSLTProcessor();
43+
$proc->registerPHPFunctions();
44+
$proc->importStylesheet($xsl);
45+
echo $proc->transformToXml($doc);
46+
47+
var_dump($stored_ns->localName);
48+
var_dump($stored_ns->namespaceURI);
49+
var_dump($stored_ns->parentNode->localName);
50+
var_dump($stored_ns->ownerDocument instanceof DOMDocument);
51+
?>
52+
--CLEAN--
53+
<?php
54+
@unlink(__DIR__ . '/gh22621_external.xml');
55+
?>
56+
--EXPECTF--
57+
<?xml version="1.0"?>
58+
<result xmlns:php="http://php.net/xsl">captured</result>
59+
string(6) "custom"
60+
string(10) "urn:custom"
61+
string(4) "root"
62+
bool(true)

0 commit comments

Comments
 (0)