Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions ext/dom/namespace_compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 46 additions & 0 deletions ext/dom/tests/modern/xml/gh22522.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php

$xml = <<<XML
<root xmlns="urn:a">
<child xmlns="">
<c:child xmlns:c="urn:c"/>
</child>
<b:sibling xmlns:b="urn:b" xmlns:d="urn:d" d:foo="bar">
<d:child xmlns:d="urn:d2"/>
</b:sibling>
</root>
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
Loading