@@ -103,6 +103,39 @@ zend_module_entry libxml_module_entry = {
103103
104104/* }}} */
105105
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+
106139static void php_libxml_unlink_entity (void * data , void * table , const xmlChar * name )
107140{
108141 xmlEntityPtr entity = data ;
@@ -211,8 +244,41 @@ static void php_libxml_node_free(xmlNodePtr node)
211244 xmlHashScan (dtd -> pentities , php_libxml_unlink_entity , dtd -> pentities );
212245 /* No unlinking of notations, see remark above at case XML_NOTATION_NODE. */
213246 }
214- ZEND_FALLTHROUGH ;
247+ xmlFreeNode (node );
248+ break ;
215249 }
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 ;
216282 default :
217283 xmlFreeNode (node );
218284 break ;
0 commit comments