Skip to content

Commit 1270135

Browse files
committed
Fix GH-19612: Mitigate libxml2 tree dictionary bug
This code is very similar to code on PHP 8.4 and higher, but the mitigation is extended to entity references and to attribute children.
1 parent a3de2ce commit 1270135

File tree

2 files changed

+83
-2
lines changed

2 files changed

+83
-2
lines changed

ext/dom/document.c

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,10 +1076,56 @@ static void php_dom_transfer_document_ref(xmlNodePtr node, php_libxml_ref_obj *n
10761076
}
10771077
}
10781078

1079+
/* Workaround for bug that was fixed in https://github.com/GNOME/libxml2/commit/4bc3ebf3eaba352fbbce2ef70ad00a3c7752478a */
1080+
#if LIBXML_VERSION < 21000
1081+
static xmlChar *libxml_copy_dicted_string(xmlDictPtr src_dict, xmlDictPtr dst_dict, xmlChar *str)
1082+
{
1083+
if (str == NULL) {
1084+
return NULL;
1085+
}
1086+
if (xmlDictOwns(src_dict, str) == 1) {
1087+
if (dst_dict == NULL) {
1088+
return xmlStrdup(str);
1089+
}
1090+
return BAD_CAST xmlDictLookup(dst_dict, str, -1);
1091+
}
1092+
return str;
1093+
}
1094+
1095+
static void libxml_fixup_name_and_content(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
1096+
{
1097+
if (node->type == XML_ENTITY_REF_NODE) {
1098+
node->children = NULL; /* Break link with original document. */
1099+
}
1100+
if (src_doc != NULL && src_doc->dict != NULL) {
1101+
ZEND_ASSERT(dst_doc != src_doc);
1102+
node->name = libxml_copy_dicted_string(src_doc->dict, dst_doc->dict, BAD_CAST node->name);
1103+
node->content = libxml_copy_dicted_string(src_doc->dict, NULL, node->content);
1104+
}
1105+
}
1106+
1107+
static void libxml_fixup_name_and_content_element(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
1108+
{
1109+
libxml_fixup_name_and_content(src_doc, dst_doc, node);
1110+
for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1111+
libxml_fixup_name_and_content(src_doc, dst_doc, (xmlNodePtr) attr);
1112+
for (xmlNodePtr attr_child = attr->children; attr_child != NULL; attr_child = attr_child->next) {
1113+
libxml_fixup_name_and_content(src_doc, dst_doc, attr_child);
1114+
}
1115+
}
1116+
1117+
for (xmlNodePtr child = node->children; child != NULL; child = child->next) {
1118+
libxml_fixup_name_and_content_element(src_doc, dst_doc, child);
1119+
}
1120+
}
1121+
#endif
1122+
10791123
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document)
10801124
{
1081-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1082-
if (nodep->doc != new_document) {
1125+
xmlDocPtr old_doc = nodep->doc;
1126+
1127+
php_libxml_invalidate_node_list_cache_from_doc(old_doc);
1128+
if (old_doc != new_document) {
10831129
php_libxml_invalidate_node_list_cache(dom_object_new_document->document);
10841130

10851131
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
@@ -1089,6 +1135,11 @@ bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, x
10891135
return false;
10901136
}
10911137

1138+
#if LIBXML_VERSION < 21000
1139+
/* Must be first before transferring the ref to ensure the old document dictionary stays alive. */
1140+
libxml_fixup_name_and_content_element(old_doc, new_document, nodep);
1141+
#endif
1142+
10921143
php_dom_transfer_document_ref(nodep, dom_object_new_document->document);
10931144
} else {
10941145
xmlUnlinkNode(nodep);

ext/dom/tests/gh19612.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
GH-19612 (Mitigate libxml2 tree dictionary bug)
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
$xml = new DOMDocument;
8+
$xml->loadXML(<<<XML
9+
<!DOCTYPE root [
10+
<!ENTITY foo "foo">
11+
]>
12+
<root><el x="&foo;"/></root>
13+
XML);
14+
$html = new DOMDocument;
15+
$html->loadHTML('<p>foo</p>', LIBXML_NOERROR);
16+
$p = $html->documentElement->firstChild->firstChild;
17+
$p->appendChild($html->adoptNode($xml->documentElement->firstElementChild->cloneNode(true)));
18+
19+
echo $html->saveXML();
20+
echo $xml->saveXML();
21+
?>
22+
--EXPECT--
23+
<?xml version="1.0" standalone="yes"?>
24+
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
25+
<html><body><p>foo<el x="&foo;"/></p></body></html>
26+
<?xml version="1.0"?>
27+
<!DOCTYPE root [
28+
<!ENTITY foo "foo">
29+
]>
30+
<root><el x="&foo;"/></root>

0 commit comments

Comments
 (0)