Skip to content

Commit b56141c

Browse files
committed
Merge branch 'PHP-8.3'
* PHP-8.3: Fix broken cache invalidation with deallocated and reallocated document node
2 parents 9aa4b2b + eebc528 commit b56141c

File tree

7 files changed

+84
-46
lines changed

7 files changed

+84
-46
lines changed

ext/dom/document.c

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ PHP_METHOD(DOMDocument, importNode)
818818
}
819819
}
820820

821-
php_libxml_invalidate_node_list_cache_from_doc(docp);
821+
php_libxml_invalidate_node_list_cache(intern->document);
822822

823823
DOM_RET_OBJ((xmlNodePtr) retnodep, &ret, intern);
824824
}
@@ -1031,7 +1031,7 @@ bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, x
10311031
{
10321032
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
10331033
if (nodep->doc != new_document) {
1034-
php_libxml_invalidate_node_list_cache_from_doc(new_document);
1034+
php_libxml_invalidate_node_list_cache(dom_object_new_document->document);
10351035

10361036
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
10371037
* and since this unlink it; the owner element will be unset (i.e. parentNode). */
@@ -1101,7 +1101,7 @@ PHP_METHOD(DOMDocument, normalizeDocument)
11011101

11021102
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
11031103

1104-
php_libxml_invalidate_node_list_cache_from_doc(docp);
1104+
php_libxml_invalidate_node_list_cache(intern->document);
11051105

11061106
dom_normalize((xmlNodePtr) docp);
11071107
}
@@ -1327,7 +1327,7 @@ static void dom_finish_loading_document(zval *this, zval *return_value, xmlDocPt
13271327
xmlDocPtr docp = (xmlDocPtr) dom_object_get_node(intern);
13281328
dom_doc_propsptr doc_prop = NULL;
13291329
if (docp != NULL) {
1330-
const php_libxml_doc_ptr *doc_ptr = docp->_private;
1330+
const php_libxml_ref_obj *doc_ptr = intern->document;
13311331
ZEND_ASSERT(doc_ptr != NULL); /* Must exist, we have a document */
13321332
old_modification_nr = doc_ptr->cache_tag.modification_nr;
13331333
php_libxml_decrement_node_ptr((php_libxml_node_object *) intern);
@@ -1348,9 +1348,8 @@ static void dom_finish_loading_document(zval *this, zval *return_value, xmlDocPt
13481348
php_libxml_increment_node_ptr((php_libxml_node_object *)intern, (xmlNodePtr)newdoc, (void *)intern);
13491349
/* Since iterators should invalidate, we need to start the modification number from the old counter */
13501350
if (old_modification_nr != 0) {
1351-
php_libxml_doc_ptr* doc_ptr = (php_libxml_doc_ptr*) ((php_libxml_node_object*) intern)->node; /* downcast */
1352-
doc_ptr->cache_tag.modification_nr = old_modification_nr;
1353-
php_libxml_invalidate_node_list_cache(doc_ptr);
1351+
intern->document->cache_tag.modification_nr = old_modification_nr;
1352+
php_libxml_invalidate_node_list_cache(intern->document);
13541353
}
13551354

13561355
RETURN_TRUE;
@@ -1611,7 +1610,7 @@ PHP_METHOD(DOMDocument, xinclude)
16111610
php_dom_remove_xinclude_nodes(root);
16121611
}
16131612

1614-
php_libxml_invalidate_node_list_cache_from_doc(docp);
1613+
php_libxml_invalidate_node_list_cache(intern->document);
16151614

16161615
if (err) {
16171616
RETVAL_LONG(err);

ext/dom/node.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ zend_result dom_node_node_value_write(dom_object *obj, zval *newval)
201201
break;
202202
}
203203

204-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
204+
php_libxml_invalidate_node_list_cache(obj->document);
205205

206206
zend_string_release_ex(str, 0);
207207
return SUCCESS;
@@ -794,7 +794,7 @@ zend_result dom_node_text_content_write(dom_object *obj, zval *newval)
794794
return FAILURE;
795795
}
796796

797-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
797+
php_libxml_invalidate_node_list_cache(obj->document);
798798

799799
/* Typed property, this is already a string */
800800
ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
@@ -919,7 +919,7 @@ PHP_METHOD(DOMNode, insertBefore)
919919
php_libxml_increment_doc_ref((php_libxml_node_object *)childobj, NULL);
920920
}
921921

922-
php_libxml_invalidate_node_list_cache_from_doc(parentp->doc);
922+
php_libxml_invalidate_node_list_cache(intern->document);
923923

924924
if (ref != NULL) {
925925
DOM_GET_OBJ(refp, ref, xmlNodePtr, refpobj);
@@ -1124,7 +1124,7 @@ PHP_METHOD(DOMNode, replaceChild)
11241124
nodep->doc->intSubset = (xmlDtd *) newchild;
11251125
}
11261126
}
1127-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1127+
php_libxml_invalidate_node_list_cache(intern->document);
11281128
DOM_RET_OBJ(oldchild, &ret, intern);
11291129
}
11301130
/* }}} end dom_node_replace_child */
@@ -1166,7 +1166,7 @@ PHP_METHOD(DOMNode, removeChild)
11661166
}
11671167

11681168
xmlUnlinkNode(child);
1169-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1169+
php_libxml_invalidate_node_list_cache(intern->document);
11701170
DOM_RET_OBJ(child, &ret, intern);
11711171
}
11721172
/* }}} end dom_node_remove_child */
@@ -1271,7 +1271,7 @@ PHP_METHOD(DOMNode, appendChild)
12711271
dom_reconcile_ns(nodep->doc, new_child);
12721272
}
12731273

1274-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1274+
php_libxml_invalidate_node_list_cache(intern->document);
12751275

12761276
DOM_RET_OBJ(new_child, &ret, intern);
12771277
return;
@@ -1387,7 +1387,7 @@ PHP_METHOD(DOMNode, normalize)
13871387

13881388
DOM_GET_OBJ(nodep, id, xmlNodePtr, intern);
13891389

1390-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1390+
php_libxml_invalidate_node_list_cache(intern->document);
13911391

13921392
dom_normalize(nodep);
13931393

ext/dom/parentnode.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc)
309309
return;
310310
}
311311

312-
php_libxml_invalidate_node_list_cache_from_doc(parentNode->doc);
312+
php_libxml_invalidate_node_list_cache(context->document);
313313

314314
xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
315315

@@ -353,7 +353,7 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc)
353353
return;
354354
}
355355

356-
php_libxml_invalidate_node_list_cache_from_doc(parentNode->doc);
356+
php_libxml_invalidate_node_list_cache(context->document);
357357

358358
xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
359359

@@ -404,7 +404,7 @@ void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
404404

405405
doc = prevsib->doc;
406406

407-
php_libxml_invalidate_node_list_cache_from_doc(doc);
407+
php_libxml_invalidate_node_list_cache(context->document);
408408

409409
/* Spec step 4: convert nodes into fragment */
410410
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
@@ -456,7 +456,7 @@ void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
456456

457457
doc = nextsib->doc;
458458

459-
php_libxml_invalidate_node_list_cache_from_doc(doc);
459+
php_libxml_invalidate_node_list_cache(context->document);
460460

461461
/* Spec step 4: convert nodes into fragment */
462462
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
@@ -523,7 +523,7 @@ void dom_child_node_remove(dom_object *context)
523523
return;
524524
}
525525

526-
php_libxml_invalidate_node_list_cache_from_doc(child->doc);
526+
php_libxml_invalidate_node_list_cache(context->document);
527527

528528
xmlUnlinkNode(child);
529529
}
@@ -557,7 +557,7 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
557557
}
558558

559559
xmlDocPtr doc = parentNode->doc;
560-
php_libxml_invalidate_node_list_cache_from_doc(doc);
560+
php_libxml_invalidate_node_list_cache(context->document);
561561

562562
/* Spec step 4: convert nodes into fragment */
563563
xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
@@ -602,7 +602,7 @@ void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t
602602
return;
603603
}
604604

605-
php_libxml_invalidate_node_list_cache_from_doc(thisp->doc);
605+
php_libxml_invalidate_node_list_cache(context->document);
606606

607607
dom_remove_all_children(thisp);
608608

ext/dom/php_dom.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ int php_dom_get_nodelist_length(dom_object *obj);
191191
#define DOM_NODELIST 0
192192
#define DOM_NAMEDNODEMAP 1
193193

194-
static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php_libxml_cache_tag *cache_tag, const php_libxml_doc_ptr *doc_ptr)
194+
static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php_libxml_cache_tag *cache_tag, const php_libxml_ref_obj *doc_ptr)
195195
{
196196
ZEND_ASSERT(cache_tag != NULL);
197197
ZEND_ASSERT(doc_ptr != NULL);
@@ -206,15 +206,26 @@ static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php
206206
static zend_always_inline bool php_dom_is_cache_tag_stale_from_node(const php_libxml_cache_tag *cache_tag, const xmlNodePtr node)
207207
{
208208
ZEND_ASSERT(node != NULL);
209-
return !node->doc || !node->doc->_private || php_dom_is_cache_tag_stale_from_doc_ptr(cache_tag, node->doc->_private);
209+
php_libxml_node_ptr *private = node->_private;
210+
if (!private) {
211+
return true;
212+
}
213+
php_libxml_node_object *object_private = private->_private;
214+
if (!object_private || !object_private->document) {
215+
return true;
216+
}
217+
return php_dom_is_cache_tag_stale_from_doc_ptr(cache_tag, object_private->document);
210218
}
211219

212220
static zend_always_inline void php_dom_mark_cache_tag_up_to_date_from_node(php_libxml_cache_tag *cache_tag, const xmlNodePtr node)
213221
{
214222
ZEND_ASSERT(cache_tag != NULL);
215-
if (node->doc && node->doc->_private) {
216-
const php_libxml_doc_ptr* doc_ptr = node->doc->_private;
217-
cache_tag->modification_nr = doc_ptr->cache_tag.modification_nr;
223+
php_libxml_node_ptr *private = node->_private;
224+
if (private) {
225+
php_libxml_node_object *object_private = private->_private;
226+
if (object_private->document) {
227+
cache_tag->modification_nr = object_private->document->cache_tag.modification_nr;
228+
}
218229
}
219230
}
220231

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
getElementsByTagName() liveness with deallocated document
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument;
9+
$dom->loadXML(<<<XML
10+
<?xml version="1.0"?>
11+
<container>
12+
<p>1</p><p>2</p><p>3</p>
13+
</container>
14+
XML);
15+
16+
$ps = $dom->documentElement->getElementsByTagName('p');
17+
$second = $ps->item(1);
18+
var_dump($second->textContent);
19+
var_dump($ps->length);
20+
21+
unset($dom);
22+
$dom = $second->ownerDocument;
23+
24+
$second->parentNode->appendChild($dom->createElement('p', '4'));
25+
var_dump($ps->length);
26+
27+
?>
28+
--EXPECT--
29+
string(1) "2"
30+
int(3)
31+
int(4)

ext/libxml/libxml.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,13 +1293,7 @@ PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object,
12931293
object->node->_private = private_data;
12941294
}
12951295
} else {
1296-
if (UNEXPECTED(node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE)) {
1297-
php_libxml_doc_ptr *doc_ptr = emalloc(sizeof(php_libxml_doc_ptr));
1298-
doc_ptr->cache_tag.modification_nr = 1; /* iterators start at 0, such that they will start in an uninitialised state */
1299-
object->node = (php_libxml_node_ptr *) doc_ptr; /* downcast */
1300-
} else {
1301-
object->node = emalloc(sizeof(php_libxml_node_ptr));
1302-
}
1296+
object->node = emalloc(sizeof(php_libxml_node_ptr));
13031297
ret_refcount = 1;
13041298
object->node->node = node;
13051299
object->node->refcount = 1;
@@ -1344,6 +1338,7 @@ PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object,
13441338
object->document->ptr = docp;
13451339
object->document->refcount = ret_refcount;
13461340
object->document->doc_props = NULL;
1341+
object->document->cache_tag.modification_nr = 1; /* iterators start at 0, such that they will start in an uninitialised state */
13471342
}
13481343

13491344
return ret_refcount;

ext/libxml/php_libxml.h

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,15 @@ typedef struct _libxml_doc_props {
5757
bool recover;
5858
} libxml_doc_props;
5959

60+
typedef struct {
61+
size_t modification_nr;
62+
} php_libxml_cache_tag;
63+
6064
typedef struct _php_libxml_ref_obj {
6165
void *ptr;
6266
int refcount;
6367
libxml_doc_props *doc_props;
68+
php_libxml_cache_tag cache_tag;
6469
} php_libxml_ref_obj;
6570

6671
typedef struct _php_libxml_node_ptr {
@@ -69,16 +74,6 @@ typedef struct _php_libxml_node_ptr {
6974
void *_private;
7075
} php_libxml_node_ptr;
7176

72-
typedef struct {
73-
size_t modification_nr;
74-
} php_libxml_cache_tag;
75-
76-
/* extends php_libxml_node_ptr */
77-
typedef struct {
78-
php_libxml_node_ptr node_ptr;
79-
php_libxml_cache_tag cache_tag;
80-
} php_libxml_doc_ptr;
81-
8277
typedef struct _php_libxml_node_object {
8378
php_libxml_node_ptr *node;
8479
php_libxml_ref_obj *document;
@@ -91,8 +86,11 @@ static inline php_libxml_node_object *php_libxml_node_fetch_object(zend_object *
9186
return (php_libxml_node_object *)((char*)(obj) - obj->handlers->offset);
9287
}
9388

94-
static zend_always_inline void php_libxml_invalidate_node_list_cache(php_libxml_doc_ptr *doc_ptr)
89+
static zend_always_inline void php_libxml_invalidate_node_list_cache(php_libxml_ref_obj *doc_ptr)
9590
{
91+
if (!doc_ptr) {
92+
return;
93+
}
9694
#if SIZEOF_SIZE_T == 8
9795
/* If one operation happens every nanosecond, then it would still require 584 years to overflow
9896
* the counter. So we'll just assume this never happens. */
@@ -108,7 +106,11 @@ static zend_always_inline void php_libxml_invalidate_node_list_cache(php_libxml_
108106
static zend_always_inline void php_libxml_invalidate_node_list_cache_from_doc(xmlDocPtr docp)
109107
{
110108
if (docp && docp->_private) { /* docp is NULL for detached nodes */
111-
php_libxml_invalidate_node_list_cache((php_libxml_doc_ptr *)docp->_private);
109+
php_libxml_node_ptr *private = docp->_private;
110+
php_libxml_node_object *object_private = private->_private;
111+
if (object_private) {
112+
php_libxml_invalidate_node_list_cache(object_private->document);
113+
}
112114
}
113115
}
114116

0 commit comments

Comments
 (0)