1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Fix bugs GH-16150 and GH-16152: intern document mismanagement

The reference counts of the internal document pointer are mismanaged.
In the case of fragments the refcount may be increased too much, while
for other cases the document reference may not be applied to all
children.

This bug existed for a long time and this doesn't reproduce (easily)
on 8.2 due to other bugs. Furthermore 8.2 will enter security mode soon,
and this change may be too risky.

Fixes GH-16150.
Fixed GH-16152.
Closes GH-16178.
This commit is contained in:
Niels Dossche
2024-10-02 22:48:16 +02:00
parent 1aeb70f83c
commit d4a4d2e7a9
4 changed files with 119 additions and 30 deletions

3
NEWS
View File

@@ -19,6 +19,9 @@ PHP NEWS
DOMElement->getAttributeNames()). (nielsdos)
. Fixed bug GH-16151 (Assertion failure in ext/dom/parentnode/tree.c).
(nielsdos)
. Fixed bug GH-16150 (Use after free in php_dom.c). (nielsdos)
. Fixed bug GH-16152 (Memory leak in DOMProcessingInstruction/DOMDocument).
(nielsdos)
- JSON:
. Fixed bug GH-15168 (stack overflow in json_encode()). (nielsdos)

View File

@@ -820,11 +820,62 @@ int dom_node_text_content_write(dom_object *obj, zval *newval)
/* }}} */
/* Returns true if the node was changed, false otherwise. */
static bool dom_set_document_ref_obj_single(xmlNodePtr node, xmlDocPtr doc, php_libxml_ref_obj *document)
{
dom_object *childobj = php_dom_object_get_data(node);
if (childobj && !childobj->document) {
childobj->document = document;
document->refcount++;
return true;
}
return false;
}
/* TODO: on 8.4 replace the loop with the tree walk helper function. */
static void dom_set_document_pointers(xmlNodePtr node, xmlDocPtr doc, php_libxml_ref_obj *document)
{
/* Applies the document to the entire subtree. */
xmlSetTreeDoc(node, doc);
if (!dom_set_document_ref_obj_single(node, doc, document)) {
return;
}
xmlNodePtr base = node;
node = node->children;
while (node != NULL) {
ZEND_ASSERT(node != base);
if (!dom_set_document_ref_obj_single(node, doc, document)) {
break;
}
if (node->type == XML_ELEMENT_NODE) {
if (node->children) {
node = node->children;
continue;
}
}
if (node->next) {
node = node->next;
} else {
/* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */
do {
node = node->parent;
if (node == base) {
return;
}
} while (node->next == NULL);
node = node->next;
}
}
}
static xmlNodePtr _php_dom_insert_fragment(xmlNodePtr nodep, xmlNodePtr prevsib, xmlNodePtr nextsib, xmlNodePtr fragment, dom_object *intern, dom_object *childobj) /* {{{ */
{
xmlNodePtr newchild, node;
newchild = fragment->children;
xmlNodePtr newchild = fragment->children;
if (newchild) {
if (prevsib == NULL) {
@@ -840,17 +891,10 @@ static xmlNodePtr _php_dom_insert_fragment(xmlNodePtr nodep, xmlNodePtr prevsib,
nextsib->prev = fragment->last;
}
node = newchild;
/* Assign parent node pointer */
xmlNodePtr node = newchild;
while (node != NULL) {
node->parent = nodep;
if (node->doc != nodep->doc) {
xmlSetTreeDoc(node, nodep->doc);
if (node->_private != NULL) {
childobj = node->_private;
childobj->document = intern->document;
php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
}
}
if (node == fragment->last) {
break;
}
@@ -930,8 +974,7 @@ PHP_METHOD(DOMNode, insertBefore)
}
if (child->doc == NULL && parentp->doc != NULL) {
childobj->document = intern->document;
php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
dom_set_document_pointers(child, parentp->doc, intern->document);
}
php_libxml_invalidate_node_list_cache(intern->document);
@@ -949,9 +992,6 @@ PHP_METHOD(DOMNode, insertBefore)
if (child->type == XML_TEXT_NODE && (refp->type == XML_TEXT_NODE ||
(refp->prev != NULL && refp->prev->type == XML_TEXT_NODE))) {
if (child->doc == NULL) {
xmlSetTreeDoc(child, parentp->doc);
}
new_child = child;
new_child->parent = refp->parent;
new_child->next = refp;
@@ -1003,9 +1043,6 @@ PHP_METHOD(DOMNode, insertBefore)
}
if (child->type == XML_TEXT_NODE && parentp->last != NULL && parentp->last->type == XML_TEXT_NODE) {
child->parent = parentp;
if (child->doc == NULL) {
xmlSetTreeDoc(child, parentp->doc);
}
new_child = child;
if (parentp->children == NULL) {
parentp->children = child;
@@ -1111,6 +1148,10 @@ PHP_METHOD(DOMNode, replaceChild)
RETURN_FALSE;
}
if (newchild->doc == NULL && nodep->doc != NULL) {
dom_set_document_pointers(newchild, nodep->doc, intern->document);
}
if (newchild->type == XML_DOCUMENT_FRAG_NODE) {
xmlNodePtr prevsib, nextsib;
prevsib = oldchild->prev;
@@ -1127,11 +1168,6 @@ PHP_METHOD(DOMNode, replaceChild)
xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
replacedoctype = (intSubset == (xmlDtd *) oldchild);
if (newchild->doc == NULL && nodep->doc != NULL) {
xmlSetTreeDoc(newchild, nodep->doc);
newchildobj->document = intern->document;
php_libxml_increment_doc_ref((php_libxml_node_object *)newchildobj, NULL);
}
xmlReplaceNode(oldchild, newchild);
dom_reconcile_ns(nodep->doc, newchild);
@@ -1216,8 +1252,7 @@ PHP_METHOD(DOMNode, appendChild)
}
if (child->doc == NULL && nodep->doc != NULL) {
childobj->document = intern->document;
php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
dom_set_document_pointers(child, nodep->doc, intern->document);
}
if (child->parent != NULL){
@@ -1226,9 +1261,6 @@ PHP_METHOD(DOMNode, appendChild)
if (child->type == XML_TEXT_NODE && nodep->last != NULL && nodep->last->type == XML_TEXT_NODE) {
child->parent = nodep;
if (child->doc == NULL) {
xmlSetTreeDoc(child, nodep->doc);
}
new_child = child;
if (nodep->children == NULL) {
nodep->children = child;

View File

@@ -0,0 +1,27 @@
--TEST--
GH-16150 (Use after free in php_dom.c)
--EXTENSIONS--
dom
--FILE--
<?php
function test($fname) {
$e1 = new DOMElement("E1");
$e2 = new DOMElement("E2");
$e3 = new DOMElement("E3");
$doc = new DOMDocument(); // Must be placed here so it is destroyed first
$doc->{$fname}($e3);
$e2->append($e1);
$e3->{$fname}($e2);
echo $doc->saveXML();
}
test('appendChild');
test('insertBefore');
?>
--EXPECT--
<?xml version="1.0"?>
<E3><E2><E1/></E2></E3>
<?xml version="1.0"?>
<E3><E2><E1/></E2></E3>

View File

@@ -0,0 +1,27 @@
--TEST--
GH-16152 (Memory leak in DOMProcessingInstruction/DOMDocument)
--EXTENSIONS--
dom
--FILE--
<?php
function test($fname) {
$doc = new DOMDocument();
$instr = new DOMProcessingInstruction("tr", "r");
$frag = new DOMDocumentFragment();
$frag2 = new DOMDocumentFragment();
$frag2->append($instr);
$frag->append($frag2);
$doc->{$fname}($frag);
echo $doc->saveXML();
}
test('insertBefore');
test('appendChild');
?>
--EXPECT--
<?xml version="1.0"?>
<?tr r?>
<?xml version="1.0"?>
<?tr r?>