Skip to content

Commit d17a997

Browse files
committed
Fix GH-21496: UAF in dom_objects_free_storage.
Cloning a non-document DOM node creates a copy within the same xmlDoc. importStylesheet then passes that original document to xsltParseStylesheetDoc, which may strip and free nodes during processing, invalidating PHP objects still referencing them. Resolve the ownerDocument for non-document nodes and clone that instead.
1 parent 97bb48e commit d17a997

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

ext/xsl/tests/gh21496.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
GH-21496 (UAF in dom_objects_free_storage when importing non-document node as stylesheet)
3+
--EXTENSIONS--
4+
dom
5+
xsl
6+
--CREDITS--
7+
YuanchengJiang
8+
--FILE--
9+
<?php
10+
$comment = new DOMComment("my value");
11+
$doc = new DOMDocument();
12+
$doc->loadXML(<<<XML
13+
<container/>
14+
XML);
15+
$doc->documentElement->appendChild($comment);
16+
$proc = new XSLTProcessor();
17+
var_dump($proc->importStylesheet($comment));
18+
?>
19+
--EXPECTF--
20+
Warning: XSLTProcessor::importStylesheet(): compilation error: file %s line 1 element container in %s on line %d
21+
22+
Warning: XSLTProcessor::importStylesheet(): xsltParseStylesheetProcess : document is not a stylesheet in %s on line %d
23+
bool(false)

ext/xsl/xsltprocessor.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,39 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
167167
xsltStylesheetPtr sheetp;
168168
bool clone_docu = false;
169169
xmlNode *nodep = NULL;
170-
zval *cloneDocu, rv, clone_zv;
170+
zval *cloneDocu, rv, clone_zv, owner_zv;
171171
zend_string *member;
172172

173173
id = ZEND_THIS;
174174
if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &docp) == FAILURE) {
175175
RETURN_THROWS();
176176
}
177177

178+
nodep = php_libxml_import_node(docp);
179+
if (nodep == NULL) {
180+
zend_argument_type_error(1, "must be a valid XML node");
181+
RETURN_THROWS();
182+
}
183+
184+
ZVAL_UNDEF(&owner_zv);
185+
186+
/* For non-document nodes, resolve the ownerDocument and clone that
187+
* instead as xsltParseStylesheetProcess may free nodes in the document. */
188+
if (nodep->type != XML_DOCUMENT_NODE && nodep->type != XML_HTML_DOCUMENT_NODE) {
189+
if (nodep->doc == NULL) {
190+
zend_argument_value_error(1, "must be part of a document");
191+
RETURN_THROWS();
192+
}
193+
194+
php_dom_create_object((xmlNodePtr) nodep->doc, &owner_zv, php_dom_obj_from_obj(Z_OBJ_P(docp)));
195+
docp = &owner_zv;
196+
}
197+
198+
if (Z_OBJ_HANDLER_P(docp, clone_obj) == NULL) {
199+
zend_argument_type_error(1, "must be a cloneable document");
200+
RETURN_THROWS();
201+
}
202+
178203
/* libxslt uses _private, so we must copy the imported
179204
* stylesheet document otherwise the node proxies will be a mess.
180205
* We will clone the object and detach the libxml internals later. */
@@ -183,6 +208,7 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
183208
RETURN_THROWS();
184209
}
185210

211+
zval_ptr_dtor(&owner_zv);
186212
ZVAL_OBJ(&clone_zv, clone);
187213
nodep = php_libxml_import_node(&clone_zv);
188214

0 commit comments

Comments
 (0)