Skip to content

Commit 30c5ae4

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix #70359 and #78577: segfaults with DOMNameSpaceNode
2 parents 981f01e + 2cbb0c0 commit 30c5ae4

8 files changed

+289
-57
lines changed

ext/dom/element.c

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ int dom_element_schema_type_info_read(dom_object *obj, zval *retval)
150150

151151
/* }}} */
152152

153+
/* Note: the object returned is not necessarily a node, but can be an attribute or a namespace declaration. */
153154
static xmlNodePtr dom_get_dom1_attribute(xmlNodePtr elem, xmlChar *name) /* {{{ */
154155
{
155156
int len;
@@ -376,25 +377,13 @@ PHP_METHOD(DOMElement, getAttributeNode)
376377
}
377378

378379
if (attrp->type == XML_NAMESPACE_DECL) {
379-
xmlNsPtr curns;
380-
xmlNodePtr nsparent;
381-
382-
nsparent = attrp->_private;
383-
curns = xmlNewNs(NULL, attrp->name, NULL);
384-
if (attrp->children) {
385-
curns->prefix = xmlStrdup((xmlChar *) attrp->children);
386-
}
387-
if (attrp->children) {
388-
attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *) attrp->children, attrp->name);
389-
} else {
390-
attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *)"xmlns", attrp->name);
391-
}
392-
attrp->type = XML_NAMESPACE_DECL;
393-
attrp->parent = nsparent;
394-
attrp->ns = curns;
380+
xmlNsPtr original = (xmlNsPtr) attrp;
381+
/* Keep parent alive, because we're a fake child. */
382+
GC_ADDREF(&intern->std);
383+
(void) php_dom_create_fake_namespace_decl(nodep, original, return_value, intern);
384+
} else {
385+
DOM_RET_OBJ((xmlNodePtr) attrp, &ret, intern);
395386
}
396-
397-
DOM_RET_OBJ((xmlNodePtr) attrp, &ret, intern);
398387
}
399388
/* }}} end dom_element_get_attribute_node */
400389

ext/dom/php_dom.c

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry;
6161

6262
zend_object_handlers dom_object_handlers;
6363
zend_object_handlers dom_nnodemap_object_handlers;
64+
zend_object_handlers dom_object_namespace_node_handlers;
6465
#ifdef LIBXML_XPATH_ENABLED
6566
zend_object_handlers dom_xpath_object_handlers;
6667
#endif
@@ -86,6 +87,9 @@ static HashTable dom_xpath_prop_handlers;
8687
#endif
8788
/* }}} */
8889

90+
static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type);
91+
static void dom_object_namespace_node_free_storage(zend_object *object);
92+
8993
typedef int (*dom_read_t)(dom_object *obj, zval *retval);
9094
typedef int (*dom_write_t)(dom_object *obj, zval *newval);
9195

@@ -573,6 +577,10 @@ PHP_MINIT_FUNCTION(dom)
573577
dom_nnodemap_object_handlers.read_dimension = dom_nodelist_read_dimension;
574578
dom_nnodemap_object_handlers.has_dimension = dom_nodelist_has_dimension;
575579

580+
memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
581+
dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std);
582+
dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
583+
576584
zend_hash_init(&classes, 0, NULL, NULL, 1);
577585

578586
dom_domexception_class_entry = register_class_DOMException(zend_ce_exception);
@@ -607,7 +615,7 @@ PHP_MINIT_FUNCTION(dom)
607615
zend_hash_add_ptr(&classes, dom_node_class_entry->name, &dom_node_prop_handlers);
608616

609617
dom_namespace_node_class_entry = register_class_DOMNameSpaceNode();
610-
dom_namespace_node_class_entry->create_object = dom_objects_new;
618+
dom_namespace_node_class_entry->create_object = dom_objects_namespace_node_new;
611619

612620
zend_hash_init(&dom_namespace_node_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
613621
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "nodeName", sizeof("nodeName")-1, dom_node_node_name_read, NULL);
@@ -980,10 +988,8 @@ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xml
980988
}
981989
/* }}} */
982990

983-
static dom_object* dom_objects_set_class(zend_class_entry *class_type) /* {{{ */
991+
static void dom_objects_set_class_ex(zend_class_entry *class_type, dom_object *intern)
984992
{
985-
dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type);
986-
987993
zend_class_entry *base_class = class_type;
988994
while ((base_class->type != ZEND_INTERNAL_CLASS || base_class->info.internal.module->module_number != dom_module_entry.module_number) && base_class->parent != NULL) {
989995
base_class = base_class->parent;
@@ -993,10 +999,14 @@ static dom_object* dom_objects_set_class(zend_class_entry *class_type) /* {{{ */
993999

9941000
zend_object_std_init(&intern->std, class_type);
9951001
object_properties_init(&intern->std, class_type);
1002+
}
9961003

1004+
static dom_object* dom_objects_set_class(zend_class_entry *class_type)
1005+
{
1006+
dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type);
1007+
dom_objects_set_class_ex(class_type, intern);
9971008
return intern;
9981009
}
999-
/* }}} */
10001010

10011011
/* {{{ dom_objects_new */
10021012
zend_object *dom_objects_new(zend_class_entry *class_type)
@@ -1007,6 +1017,25 @@ zend_object *dom_objects_new(zend_class_entry *class_type)
10071017
}
10081018
/* }}} */
10091019

1020+
static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type)
1021+
{
1022+
dom_object_namespace_node *intern = zend_object_alloc(sizeof(dom_object_namespace_node), class_type);
1023+
dom_objects_set_class_ex(class_type, &intern->dom);
1024+
intern->dom.std.handlers = &dom_object_namespace_node_handlers;
1025+
return &intern->dom.std;
1026+
}
1027+
1028+
static void dom_object_namespace_node_free_storage(zend_object *object)
1029+
{
1030+
dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(object);
1031+
if (intern->parent_intern != NULL) {
1032+
zval tmp;
1033+
ZVAL_OBJ(&tmp, &intern->parent_intern->std);
1034+
zval_ptr_dtor(&tmp);
1035+
}
1036+
dom_objects_free_storage(object);
1037+
}
1038+
10101039
#ifdef LIBXML_XPATH_ENABLED
10111040
/* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
10121041
zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
@@ -1579,6 +1608,28 @@ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) {
15791608
}
15801609
/* }}} end dom_get_nsdecl */
15811610

1611+
/* Note: Assumes the additional lifetime was already added in the caller. */
1612+
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern)
1613+
{
1614+
xmlNodePtr attrp;
1615+
xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL);
1616+
if (original->prefix) {
1617+
curns->prefix = xmlStrdup(original->prefix);
1618+
attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *) original->prefix, original->href);
1619+
} else {
1620+
attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *)"xmlns", original->href);
1621+
}
1622+
attrp->type = XML_NAMESPACE_DECL;
1623+
attrp->parent = nodep;
1624+
attrp->ns = curns;
1625+
1626+
php_dom_create_object(attrp, return_value, parent_intern);
1627+
/* This object must exist, because we just created an object for it via php_dom_create_object(). */
1628+
dom_object *obj = ((php_libxml_node_ptr *)attrp->_private)->_private;
1629+
php_dom_namespace_node_obj_from_obj(&obj->std)->parent_intern = parent_intern;
1630+
return attrp;
1631+
}
1632+
15821633
static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */
15831634
{
15841635
zval offset_copy;

ext/dom/php_dom.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ typedef struct {
100100
php_libxml_cache_tag cache_tag;
101101
} php_dom_iterator;
102102

103+
typedef struct {
104+
/* This may be a fake object that isn't actually in the children list of the parent.
105+
* This is because some namespace declaration nodes aren't stored on the parent in libxml2, so we have to fake it.
106+
* We could use a zval for this, but since this is always going to be an object let's save space... */
107+
dom_object *parent_intern;
108+
dom_object dom;
109+
} dom_object_namespace_node;
110+
111+
static inline dom_object_namespace_node *php_dom_namespace_node_obj_from_obj(zend_object *obj) {
112+
return (dom_object_namespace_node*)((char*)(obj) - XtOffsetOf(dom_object_namespace_node, dom.std));
113+
}
114+
103115
#include "domexception.h"
104116

105117
dom_object *dom_object_get_data(xmlNodePtr obj);
@@ -134,6 +146,7 @@ xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, int index);
134146
xmlNode *php_dom_libxml_notation_iter(xmlHashTable *ht, int index);
135147
zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
136148
void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce);
149+
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern);
137150

138151
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);
139152
void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc);

ext/dom/tests/bug70359.phpt

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
--TEST--
2+
Bug #70359 (print_r() on DOMAttr causes Segfault in php_libxml_node_free_list())
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
echo "-- Test without parent --\n";
9+
10+
$dom = new DOMDocument();
11+
$dom->loadXML(<<<XML
12+
<?xml version="1.0" encoding="UTF-8"?>
13+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="fooooooooooooooooooooo"/>
14+
XML);
15+
$spaceNode = $dom->documentElement->getAttributeNode('xmlns');
16+
print_r($spaceNode);
17+
18+
echo "-- Test with parent and non-ns attribute --\n";
19+
20+
$dom = new DOMDocument();
21+
$dom->loadXML(<<<XML
22+
<?xml version="1.0" encoding="UTF-8"?>
23+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
24+
<url xmlns:xsi="fooooooooooooooooooooo" myattrib="bar"/>
25+
</urlset>
26+
XML);
27+
$spaceNode = $dom->documentElement->firstElementChild->getAttributeNode('myattrib');
28+
var_dump($spaceNode->nodeType);
29+
var_dump($spaceNode->nodeValue);
30+
31+
$dom->documentElement->firstElementChild->remove();
32+
try {
33+
print_r($spaceNode->parentNode);
34+
} catch (\Error $e) {
35+
echo $e->getMessage(), "\n";
36+
}
37+
38+
echo "-- Test with parent and ns attribute --\n";
39+
40+
$dom = new DOMDocument();
41+
$dom->loadXML(<<<XML
42+
<?xml version="1.0" encoding="UTF-8"?>
43+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
44+
<url xmlns:xsi="fooooooooooooooooooooo" myattrib="bar"/>
45+
</urlset>
46+
XML);
47+
$spaceNode = $dom->documentElement->firstElementChild->getAttributeNode('xmlns:xsi');
48+
print_r($spaceNode);
49+
50+
$dom->documentElement->firstElementChild->remove();
51+
var_dump($spaceNode->parentNode->nodeName); // Shouldn't crash
52+
53+
?>
54+
--EXPECT--
55+
-- Test without parent --
56+
DOMNameSpaceNode Object
57+
(
58+
[nodeName] => xmlns
59+
[nodeValue] => http://www.sitemaps.org/schemas/sitemap/0.9
60+
[nodeType] => 18
61+
[prefix] =>
62+
[localName] => xmlns
63+
[namespaceURI] => http://www.sitemaps.org/schemas/sitemap/0.9
64+
[ownerDocument] => (object value omitted)
65+
[parentNode] => (object value omitted)
66+
)
67+
-- Test with parent and non-ns attribute --
68+
int(2)
69+
string(3) "bar"
70+
Couldn't fetch DOMAttr. Node no longer exists
71+
-- Test with parent and ns attribute --
72+
DOMNameSpaceNode Object
73+
(
74+
[nodeName] => xmlns:xsi
75+
[nodeValue] => fooooooooooooooooooooo
76+
[nodeType] => 18
77+
[prefix] => xsi
78+
[localName] => xsi
79+
[namespaceURI] => fooooooooooooooooooooo
80+
[ownerDocument] => (object value omitted)
81+
[parentNode] => (object value omitted)
82+
)
83+
string(3) "url"

ext/dom/tests/bug78577.phpt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Bug #78577 (Crash in DOMNameSpace debug info handlers)
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$doc = new DOMDocument;
9+
$doc->loadXML('<foo xmlns="http://php.net/test" xmlns:foo="urn:foo" />');
10+
11+
$attr = $doc->documentElement->getAttributeNode('xmlns');
12+
var_dump($attr);
13+
14+
?>
15+
--EXPECT--
16+
object(DOMNameSpaceNode)#3 (8) {
17+
["nodeName"]=>
18+
string(5) "xmlns"
19+
["nodeValue"]=>
20+
string(19) "http://php.net/test"
21+
["nodeType"]=>
22+
int(18)
23+
["prefix"]=>
24+
string(0) ""
25+
["localName"]=>
26+
string(5) "xmlns"
27+
["namespaceURI"]=>
28+
string(19) "http://php.net/test"
29+
["ownerDocument"]=>
30+
string(22) "(object value omitted)"
31+
["parentNode"]=>
32+
string(22) "(object value omitted)"
33+
}

ext/dom/tests/xpath_domnamespacenode.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var_dump($nodes->item(0));
1717

1818
?>
1919
--EXPECT--
20-
object(DOMNameSpaceNode)#3 (8) {
20+
object(DOMNameSpaceNode)#4 (8) {
2121
["nodeName"]=>
2222
string(9) "xmlns:xml"
2323
["nodeValue"]=>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
DOMXPath::query() can return DOMNodeList with DOMNameSpaceNode items - advanced variation
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument();
9+
$dom->loadXML(<<<'XML'
10+
<root xmlns:foo="http://example.com/foo" xmlns:bar="http://example.com/bar">
11+
<child xmlns:baz="http://example.com/baz">Hello PHP!</child>
12+
</root>
13+
XML);
14+
15+
$xpath = new DOMXPath($dom);
16+
$query = '//namespace::*';
17+
18+
echo "-- All namespace attributes --\n";
19+
20+
foreach ($xpath->query($query) as $attribute) {
21+
echo $attribute->nodeName . ' = ' . $attribute->nodeValue . PHP_EOL;
22+
var_dump($attribute->parentNode->tagName);
23+
}
24+
25+
echo "-- All namespace attributes with removal attempt --\n";
26+
27+
foreach ($xpath->query($query) as $attribute) {
28+
echo "Before: ", $attribute->parentNode->tagName, "\n";
29+
// Second & third attempt should fail because it's no longer in the document
30+
try {
31+
$attribute->parentNode->remove();
32+
} catch (\DOMException $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
// However, it should not cause a use-after-free
36+
echo "After: ", $attribute->parentNode->tagName, "\n";
37+
}
38+
39+
?>
40+
--EXPECT--
41+
-- All namespace attributes --
42+
xmlns:xml = http://www.w3.org/XML/1998/namespace
43+
string(4) "root"
44+
xmlns:bar = http://example.com/bar
45+
string(4) "root"
46+
xmlns:foo = http://example.com/foo
47+
string(4) "root"
48+
xmlns:xml = http://www.w3.org/XML/1998/namespace
49+
string(5) "child"
50+
xmlns:bar = http://example.com/bar
51+
string(5) "child"
52+
xmlns:foo = http://example.com/foo
53+
string(5) "child"
54+
xmlns:baz = http://example.com/baz
55+
string(5) "child"
56+
-- All namespace attributes with removal attempt --
57+
Before: root
58+
After: root
59+
Before: root
60+
Not Found Error
61+
After: root
62+
Before: root
63+
Not Found Error
64+
After: root
65+
Before: child
66+
After: child
67+
Before: child
68+
Not Found Error
69+
After: child
70+
Before: child
71+
Not Found Error
72+
After: child
73+
Before: child
74+
Not Found Error
75+
After: child

0 commit comments

Comments
 (0)