Skip to content

Commit 080fd14

Browse files
committed
Fix phpGH-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. Closes phpGH-19620.
1 parent f9ce6d8 commit 080fd14

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ PHP NEWS
1515
. Fixed date_sunrise() and date_sunset() with partial-hour UTC offset.
1616
(ilutov)
1717

18+
- DOM:
19+
. Fixed bug GH-19612 (Mitigate libxml2 tree dictionary bug). (nielsdos)
20+
1821
- FPM:
1922
. Fixed failed debug assertion when php_admin_value setting fails. (ilutov)
2023

ext/dom/document.c

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,10 +1076,62 @@ 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+
* and https://github.com/GNOME/libxml2/commit/bc7ab5a2e61e4b36accf6803c5b0e245c11154b1 */
1081+
#if LIBXML_VERSION < 21300
1082+
static xmlChar *libxml_copy_dicted_string(xmlDictPtr src_dict, xmlDictPtr dst_dict, xmlChar *str)
1083+
{
1084+
if (str == NULL) {
1085+
return NULL;
1086+
}
1087+
if (xmlDictOwns(src_dict, str) == 1) {
1088+
if (dst_dict == NULL) {
1089+
return xmlStrdup(str);
1090+
}
1091+
return BAD_CAST xmlDictLookup(dst_dict, str, -1);
1092+
}
1093+
return str;
1094+
}
1095+
1096+
static void libxml_fixup_name_and_content(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
1097+
{
1098+
if (node->type == XML_ENTITY_REF_NODE) {
1099+
node->children = NULL; /* Break link with original document. */
1100+
}
1101+
if (src_doc != NULL && src_doc->dict != NULL) {
1102+
ZEND_ASSERT(dst_doc != src_doc);
1103+
node->name = libxml_copy_dicted_string(src_doc->dict, dst_doc->dict, BAD_CAST node->name);
1104+
node->content = libxml_copy_dicted_string(src_doc->dict, NULL, node->content);
1105+
}
1106+
}
1107+
1108+
static void libxml_fixup_name_and_content_outer(xmlDocPtr src_doc, xmlDocPtr dst_doc, xmlNodePtr node)
1109+
{
1110+
libxml_fixup_name_and_content(src_doc, dst_doc, node);
1111+
1112+
if (node->type == XML_ELEMENT_NODE) {
1113+
for (xmlAttrPtr attr = node->properties; attr != NULL; attr = attr->next) {
1114+
libxml_fixup_name_and_content(src_doc, dst_doc, (xmlNodePtr) attr);
1115+
for (xmlNodePtr attr_child = attr->children; attr_child != NULL; attr_child = attr_child->next) {
1116+
libxml_fixup_name_and_content(src_doc, dst_doc, attr_child);
1117+
}
1118+
}
1119+
}
1120+
1121+
if (node->type == XML_ELEMENT_NODE || node->type == XML_ATTRIBUTE_NODE) {
1122+
for (xmlNodePtr child = node->children; child != NULL; child = child->next) {
1123+
libxml_fixup_name_and_content_outer(src_doc, dst_doc, child);
1124+
}
1125+
}
1126+
}
1127+
#endif
1128+
10791129
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document)
10801130
{
1081-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1082-
if (nodep->doc != new_document) {
1131+
xmlDocPtr old_doc = nodep->doc;
1132+
1133+
php_libxml_invalidate_node_list_cache_from_doc(old_doc);
1134+
if (old_doc != new_document) {
10831135
php_libxml_invalidate_node_list_cache(dom_object_new_document->document);
10841136

10851137
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
@@ -1089,6 +1141,11 @@ bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, x
10891141
return false;
10901142
}
10911143

1144+
#if LIBXML_VERSION < 21300
1145+
/* Must be first before transferring the ref to ensure the old document dictionary stays alive. */
1146+
libxml_fixup_name_and_content_outer(old_doc, new_document, nodep);
1147+
#endif
1148+
10921149
php_dom_transfer_document_ref(nodep, dom_object_new_document->document);
10931150
} else {
10941151
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)