diff --git a/ext/dom/namespace_compat.c b/ext/dom/namespace_compat.c index 1815b2dd4413..0fcacffe42ef 100644 --- a/ext/dom/namespace_compat.c +++ b/ext/dom/namespace_compat.c @@ -240,32 +240,34 @@ PHP_DOM_EXPORT void php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapp /* We want to prepend at the front, but in order of the namespace definitions. * So temporarily unlink the existing properties and add them again at the end. */ - xmlAttrPtr attr = node->properties; - node->properties = NULL; + xmlAttrPtr first_original = node->properties; + xmlAttrPtr first_ns_attr = NULL, last_ns_attr = NULL; xmlNsPtr ns = node->nsDef; - xmlAttrPtr last_added = NULL; do { - last_added = php_dom_ns_compat_mark_attribute(mapper, node, ns); - php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(mapper, ns); xmlNsPtr next = ns->next; + node->nsDef = next; ns->next = NULL; php_libxml_set_old_ns(node->doc, ns); + xmlAttrPtr added = php_dom_ns_compat_mark_attribute(mapper, node, ns); + if (UNEXPECTED(added != NULL)) { + if (first_ns_attr == NULL) { + first_ns_attr = added; + } + last_ns_attr = added; + } + php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(mapper, ns); ns = next; } while (ns != NULL); - if (last_added != NULL) { - /* node->properties now points to the first namespace declaration attribute. */ - if (attr != NULL) { - last_added->next = attr; - attr->prev = last_added; - } - } else { - /* Nothing added, so nothing changed. Only really possible on OOM. */ - node->properties = attr; + if (first_ns_attr != NULL && first_original != NULL) { + xmlAttrPtr last_original = first_ns_attr->prev; + last_original->next = NULL; + first_ns_attr->prev = NULL; + last_ns_attr->next = first_original; + first_original->prev = last_ns_attr; + node->properties = first_ns_attr; } - - node->nsDef = NULL; } PHP_DOM_EXPORT bool php_dom_ns_is_fast_ex(xmlNsPtr ns, const php_dom_ns_magic_token *magic_token) diff --git a/ext/dom/tests/modern/xml/gh22522.phpt b/ext/dom/tests/modern/xml/gh22522.phpt new file mode 100644 index 000000000000..4024de21ee8c --- /dev/null +++ b/ext/dom/tests/modern/xml/gh22522.phpt @@ -0,0 +1,46 @@ +--TEST-- +GH-22522 (Double-free of namespaces when re-parsing while iterating in-scope namespaces) +--CREDITS-- +Yuancheng Jiang (YuanchengJiang) +--EXTENSIONS-- +dom +--FILE-- + + + + + + + + +XML; + +function dump($dom, $name, $depth) { + $list = $dom->getElementsByTagName($name)[0]->getInScopeNamespaces(); + foreach ($list as $entry) { + // Re-parse namespace-heavy markup while a snapshot of the in-scope + // namespaces is still alive; used to double-free on teardown. + $reparsed = Dom\XMLDocument::createFromString($GLOBALS['xml']); + if ($depth > 0) { + dump($reparsed, 'c:child', $depth - 1); + dump($reparsed, 'child', $depth - 1); + } + echo $entry->prefix, " => ", $entry->namespaceURI, "\n"; + } +} + +$dom = Dom\XMLDocument::createFromString($xml); +dump($dom, 'c:child', 3); +dump($dom, 'child', 3); +echo "done\n"; + +?> +--EXPECT-- +c => urn:c +c => urn:c +c => urn:c +c => urn:c +done