@@ -103,6 +103,39 @@ zend_module_entry libxml_module_entry = {
103
103
104
104
/* }}} */
105
105
106
+ static void php_libxml_set_old_ns_list (xmlDocPtr doc , xmlNsPtr first , xmlNsPtr last )
107
+ {
108
+ if (UNEXPECTED (doc == NULL )) {
109
+ return ;
110
+ }
111
+
112
+ ZEND_ASSERT (last -> next == NULL );
113
+
114
+ /* Note: we'll use a prepend strategy instead of append to
115
+ * make sure we don't lose performance when the list is long.
116
+ * As libxml2 could assume the xml node is the first one, we'll place our
117
+ * new entries after the first one. */
118
+
119
+ if (UNEXPECTED (doc -> oldNs == NULL )) {
120
+ doc -> oldNs = (xmlNsPtr ) xmlMalloc (sizeof (xmlNs ));
121
+ if (doc -> oldNs == NULL ) {
122
+ return ;
123
+ }
124
+ memset (doc -> oldNs , 0 , sizeof (xmlNs ));
125
+ doc -> oldNs -> type = XML_LOCAL_NAMESPACE ;
126
+ doc -> oldNs -> href = xmlStrdup (XML_XML_NAMESPACE );
127
+ doc -> oldNs -> prefix = xmlStrdup ((const xmlChar * )"xml" );
128
+ } else {
129
+ last -> next = doc -> oldNs -> next ;
130
+ }
131
+ doc -> oldNs -> next = first ;
132
+ }
133
+
134
+ PHP_LIBXML_API void php_libxml_set_old_ns (xmlDocPtr doc , xmlNsPtr ns )
135
+ {
136
+ php_libxml_set_old_ns_list (doc , ns , ns );
137
+ }
138
+
106
139
static void php_libxml_unlink_entity (void * data , void * table , const xmlChar * name )
107
140
{
108
141
xmlEntityPtr entity = data ;
@@ -211,8 +244,41 @@ static void php_libxml_node_free(xmlNodePtr node)
211
244
xmlHashScan (dtd -> pentities , php_libxml_unlink_entity , dtd -> pentities );
212
245
/* No unlinking of notations, see remark above at case XML_NOTATION_NODE. */
213
246
}
214
- ZEND_FALLTHROUGH ;
247
+ xmlFreeNode (node );
248
+ break ;
215
249
}
250
+ case XML_ELEMENT_NODE :
251
+ if (node -> nsDef && node -> doc ) {
252
+ /* Make the namespace declaration survive the destruction of the holding element.
253
+ * This prevents a use-after-free on the namespace declaration.
254
+ *
255
+ * The main problem is that libxml2 doesn't have a reference count on the namespace declaration.
256
+ * We don't actually need to save the namespace declaration if we know the subtree it belongs to
257
+ * has no references from userland. However, we can't know that without traversing the whole subtree
258
+ * (=> slow), or without adding some subtree metadata (=> also slow).
259
+ * So we have to assume we need to save everything.
260
+ *
261
+ * However, namespace declarations are quite rare in comparison to other node types.
262
+ * Most node types are either elements, text or attributes.
263
+ * And you only need one namespace declaration per namespace (in principle).
264
+ * So I expect the number of namespace declarations to be low for an average XML document.
265
+ *
266
+ * In the worst possible case we have to save all namespace declarations when we for example remove
267
+ * the whole document. But given the above reasoning this likely won't be a lot of declarations even
268
+ * in the worst case.
269
+ * A single declaration only takes about 48 bytes of memory, and I don't expect the worst case to occur
270
+ * very often (why would you remove the whole document?).
271
+ */
272
+ xmlNsPtr ns = node -> nsDef ;
273
+ xmlNsPtr last = ns ;
274
+ while (last -> next ) {
275
+ last = last -> next ;
276
+ }
277
+ php_libxml_set_old_ns_list (node -> doc , ns , last );
278
+ node -> nsDef = NULL ;
279
+ }
280
+ xmlFreeNode (node );
281
+ break ;
216
282
default :
217
283
xmlFreeNode (node );
218
284
break ;
0 commit comments