Skip to content

Commit 120bd36

Browse files
committed
Fix crashes with entity references and predefined entities
Closes GH-13004.
1 parent 42cbace commit 120bd36

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ PHP NEWS
1515
. Fixed bug GH-12996 (Incorrect SCRIPT_NAME with Apache ProxyPassMatch when
1616
plus in path). (Jakub Zelenka)
1717

18+
- LibXML:
19+
. Fix crashes with entity references and predefined entities. (nielsdos)
20+
1821
- Opcache:
1922
. Fixed bug GH-13145 (strtok() is not comptime). (ilutov)
2023
. Fixed type inference of range(). (ilutov)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Freeing of a predefined DOMEntityReference
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
$ref = new DOMEntityReference("amp");
8+
var_dump($ref);
9+
?>
10+
--EXPECT--
11+
object(DOMEntityReference)#1 (17) {
12+
["nodeName"]=>
13+
string(3) "amp"
14+
["nodeValue"]=>
15+
NULL
16+
["nodeType"]=>
17+
int(5)
18+
["parentNode"]=>
19+
NULL
20+
["parentElement"]=>
21+
NULL
22+
["childNodes"]=>
23+
string(22) "(object value omitted)"
24+
["firstChild"]=>
25+
string(22) "(object value omitted)"
26+
["lastChild"]=>
27+
string(22) "(object value omitted)"
28+
["previousSibling"]=>
29+
NULL
30+
["nextSibling"]=>
31+
NULL
32+
["attributes"]=>
33+
NULL
34+
["isConnected"]=>
35+
bool(false)
36+
["namespaceURI"]=>
37+
NULL
38+
["prefix"]=>
39+
string(0) ""
40+
["localName"]=>
41+
NULL
42+
["baseURI"]=>
43+
NULL
44+
["textContent"]=>
45+
string(0) ""
46+
}

ext/dom/tests/delayed_freeing/entity_declaration.phpt

+17-3
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,30 @@ $doc->loadXML(<<<'XML'
99
<?xml version="1.0"?>
1010
<!DOCTYPE books [
1111
<!ENTITY test "entity is only for test purposes">
12+
<!ENTITY myimage PUBLIC "-" "mypicture.gif" NDATA GIF>
1213
]>
1314
<container/>
1415
XML);
15-
$entity = $doc->doctype->entities[0];
16-
var_dump($entity->nodeName, $entity->parentNode->nodeName);
16+
$ref1 = $doc->createEntityReference("test");
17+
$ref2 = $doc->createEntityReference("myimage");
18+
$entity1 = $doc->doctype->entities[0];
19+
$entity2 = $doc->doctype->entities[1];
20+
if (strcmp($entity1->nodeName, $entity2->nodeName) < 0) {
21+
// Entity ordering depends on the addresses
22+
[$entity1, $entity2] = [$entity2, $entity1];
23+
}
24+
var_dump($entity1->nodeName, $entity1->parentNode->nodeName);
25+
var_dump($entity2->nodeName, $entity2->parentNode->nodeName);
1726
$doc->removeChild($doc->doctype);
18-
var_dump($entity->nodeName, $entity->parentNode);
27+
var_dump($entity1->nodeName, $entity1->parentNode);
28+
var_dump($entity2->nodeName, $entity2->parentNode);
1929
?>
2030
--EXPECT--
2131
string(4) "test"
2232
string(5) "books"
33+
string(7) "myimage"
34+
string(5) "books"
2335
string(4) "test"
2436
NULL
37+
string(7) "myimage"
38+
NULL

ext/libxml/libxml.c

+38-5
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,36 @@ static void php_libxml_node_free(xmlNodePtr node)
207207
* dtd is attached to the document. This works around the issue by inspecting the parent directly. */
208208
case XML_ENTITY_DECL: {
209209
xmlEntityPtr entity = (xmlEntityPtr) node;
210-
php_libxml_unlink_entity_decl(entity);
211-
if (entity->orig != NULL) {
212-
xmlFree((char *) entity->orig);
213-
entity->orig = NULL;
210+
if (entity->etype != XML_INTERNAL_PREDEFINED_ENTITY) {
211+
php_libxml_unlink_entity_decl(entity);
212+
#if LIBXML_VERSION >= 21200
213+
xmlFreeEntity(entity);
214+
#else
215+
if (entity->children != NULL && entity->owner && entity == (xmlEntityPtr) entity->children->parent) {
216+
xmlFreeNodeList(entity->children);
217+
}
218+
xmlDictPtr dict = entity->doc != NULL ? entity->doc->dict : NULL;
219+
if (dict == NULL || !xmlDictOwns(dict, entity->name)) {
220+
xmlFree((xmlChar *) entity->name);
221+
}
222+
if (dict == NULL || !xmlDictOwns(dict, entity->ExternalID)) {
223+
xmlFree((xmlChar *) entity->ExternalID);
224+
}
225+
if (dict == NULL || !xmlDictOwns(dict, entity->SystemID)) {
226+
xmlFree((xmlChar *) entity->SystemID);
227+
}
228+
if (dict == NULL || !xmlDictOwns(dict, entity->URI)) {
229+
xmlFree((xmlChar *) entity->URI);
230+
}
231+
if (dict == NULL || !xmlDictOwns(dict, entity->content)) {
232+
xmlFree(entity->content);
233+
}
234+
if (dict == NULL || !xmlDictOwns(dict, entity->orig)) {
235+
xmlFree(entity->orig);
236+
}
237+
xmlFree(entity);
238+
#endif
214239
}
215-
xmlFreeNode(node);
216240
break;
217241
}
218242
case XML_NOTATION_NODE: {
@@ -1386,6 +1410,15 @@ PHP_LIBXML_API void php_libxml_node_free_resource(xmlNodePtr node)
13861410
case XML_DOCUMENT_NODE:
13871411
case XML_HTML_DOCUMENT_NODE:
13881412
break;
1413+
case XML_ENTITY_REF_NODE:
1414+
/* Entity reference nodes are special: their children point to entity declarations,
1415+
* but they don't own the declarations and therefore shouldn't free the children.
1416+
* Moreover, there can be more than one reference node for a single entity declarations. */
1417+
php_libxml_unregister_node(node);
1418+
if (node->parent == NULL) {
1419+
php_libxml_node_free(node);
1420+
}
1421+
break;
13891422
default:
13901423
if (node->parent == NULL || node->type == XML_NAMESPACE_DECL) {
13911424
php_libxml_node_free_list((xmlNodePtr) node->children);

0 commit comments

Comments
 (0)