From df3c26e1cc4464e775c0d27dd22cf552e3320d0c Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 1 Jul 2026 18:36:27 +0100 Subject: [PATCH 1/2] ext/dom: Fix double-free of namespaces on OOM during parsing. Fix #22522 php_dom_ns_compat_mark_attribute_list() cleared node->nsDef only after moving every namespace onto doc->oldNs, so a bailout mid-loop (e.g. OOM) left both lists owning the same namespaces, double-freeing them on teardown. Move each namespace to oldNs before the allocating calls so it is only ever owned by one list. --- ext/dom/namespace_compat.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ext/dom/namespace_compat.c b/ext/dom/namespace_compat.c index 1815b2dd4413..d4fe17615f88 100644 --- a/ext/dom/namespace_compat.c +++ b/ext/dom/namespace_compat.c @@ -246,11 +246,12 @@ PHP_DOM_EXPORT void php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapp 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); + last_added = php_dom_ns_compat_mark_attribute(mapper, node, ns); + php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(mapper, ns); ns = next; } while (ns != NULL); @@ -264,8 +265,6 @@ PHP_DOM_EXPORT void php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapp /* Nothing added, so nothing changed. Only really possible on OOM. */ node->properties = attr; } - - node->nsDef = NULL; } PHP_DOM_EXPORT bool php_dom_ns_is_fast_ex(xmlNsPtr ns, const php_dom_ns_magic_token *magic_token) From 04edf4cdc0e34e5a61118f30d6188eea61223ab1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 2 Jul 2026 14:53:30 +0100 Subject: [PATCH 2/2] attempt to fix leak --- ext/dom/namespace_compat.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/ext/dom/namespace_compat.c b/ext/dom/namespace_compat.c index d4fe17615f88..0fcacffe42ef 100644 --- a/ext/dom/namespace_compat.c +++ b/ext/dom/namespace_compat.c @@ -240,30 +240,33 @@ 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 { xmlNsPtr next = ns->next; node->nsDef = next; ns->next = NULL; php_libxml_set_old_ns(node->doc, ns); - last_added = php_dom_ns_compat_mark_attribute(mapper, node, 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; } }