Skip to content

Commit 9073875

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. close GH-22623
1 parent 65a69b4 commit 9073875

3 files changed

Lines changed: 68 additions & 3 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ PHP NEWS
3333
. Fixed Dom\DtdNamedNodeMap integer dimension access so negative indexes
3434
return NULL and indexes outside the int range throw ValueError instead of
3535
returning the first entity or notation. (Weilin Du)
36+
. Fixed bug GH-22623 (use after free with namespace nodes from
37+
XSLTProcessor::registerFunctions())/ (David Carlier)
3638

3739
- Exif:
3840
. Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"

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

0 commit comments

Comments
 (0)