Description
Dom\XMLDocument::C14N() produces duplicate xmlns prefix declarations after calling setAttributeNS() to add a prefixed xmlns attribute on an element that already has a default xmlns and at least one non-xmlns attribute.
The saveXML() output is correct; only C14N() is affected.
Reproducer: https://3v4l.org/dDYik#v8.5.3
<?php
echo "PHP " . PHP_VERSION . PHP_EOL;
echo "libxml " . LIBXML_DOTTED_VERSION . PHP_EOL . PHP_EOL;
$doc = Dom\XMLDocument::createFromString('<root xmlns="urn:a" attr="val"/>');
$doc->documentElement->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ns1", "urn:a");
echo "saveXML: " . trim($doc->saveXML()) . PHP_EOL;
echo "C14N: " . $doc->C14N() . PHP_EOL;
// saveXML (correct):
// <root xmlns="urn:a" attr="val" xmlns:ns1="urn:a"/>
//
// C14N (BUGGY - xmlns:ns1 appears TWICE):
// <root xmlns="urn:a" xmlns:ns1="urn:a" attr="val" xmlns:ns1="urn:a"></root>
Confirmed on 3v4l.org with PHP 8.5.3 / libxml 2.9.14.
Conditions
All three conditions must be met for the bug to trigger:
- Element has a default xmlns (
xmlns="...")
- Element has at least one non-xmlns attribute (e.g.
attr="val")
setAttributeNS() adds a prefixed xmlns pointing to the same URI as the default xmlns
Removing any of these conditions produces correct C14N output. Element::rename() is NOT required.
Secondary issue: parsing the invalid C14N output
Attempting to parse the invalid C14N output with createFromString() correctly throws a DOMException on 3v4l.org ("Attribute xmlns:ns1 redefined"). However, this behavior appears to be very machine-dependent. In some environments (Docker docphpro/php84, GitHub Actions Ubuntu 24.04), parsing this same invalid output instead hangs indefinitely or crashes with memory corruption:
free(): double free detected in tcache 2
or:
munmap_chunk(): invalid pointer
This means the impact ranges from a catchable exception to a hard process crash depending on the environment.
Real-world impact
The comparable() configurator in veewee/xml calls optimize_namespaces() (which uses setAttributeNS + Element::rename) followed by canonicalize() (which calls C14N()). The invalid C14N output causes createFromString() to fail, breaking test suites that compare XML documents. On GitHub Actions (Ubuntu 24.04), this manifests as tests hanging until timeout.
Workaround: round-trip through saveXML() / createFromString() before calling C14N() to normalize namespace declarations. See veewee/xml#101 for an example.
Related
/cc @ndossche
PHP Version
Reproduced on: PHP 8.4.19, PHP 8.5.3, PHP 8.5.4 (all with libxml 2.9.14)
Not reproducible on: PHP 8.4.13 with libxml 2.9.13 (macOS)
Operating System
Ubuntu 24.04 (libxml 2.9.14), also confirmed on 3v4l.org.
Not reproducible on macOS with libxml 2.9.13.
Description
Dom\XMLDocument::C14N()produces duplicate xmlns prefix declarations after callingsetAttributeNS()to add a prefixed xmlns attribute on an element that already has a default xmlns and at least one non-xmlns attribute.The
saveXML()output is correct; onlyC14N()is affected.Reproducer: https://3v4l.org/dDYik#v8.5.3
Confirmed on 3v4l.org with PHP 8.5.3 / libxml 2.9.14.
Conditions
All three conditions must be met for the bug to trigger:
xmlns="...")attr="val")setAttributeNS()adds a prefixed xmlns pointing to the same URI as the default xmlnsRemoving any of these conditions produces correct C14N output.
Element::rename()is NOT required.Secondary issue: parsing the invalid C14N output
Attempting to parse the invalid C14N output with
createFromString()correctly throws aDOMExceptionon 3v4l.org ("Attribute xmlns:ns1 redefined"). However, this behavior appears to be very machine-dependent. In some environments (Dockerdocphpro/php84, GitHub Actions Ubuntu 24.04), parsing this same invalid output instead hangs indefinitely or crashes with memory corruption:or:
This means the impact ranges from a catchable exception to a hard process crash depending on the environment.
Real-world impact
The
comparable()configurator in veewee/xml callsoptimize_namespaces()(which usessetAttributeNS+Element::rename) followed bycanonicalize()(which callsC14N()). The invalid C14N output causescreateFromString()to fail, breaking test suites that compare XML documents. On GitHub Actions (Ubuntu 24.04), this manifests as tests hanging until timeout.Workaround: round-trip through
saveXML()/createFromString()before callingC14N()to normalize namespace declarations. See veewee/xml#101 for an example.Related
/cc @ndossche
PHP Version
Operating System
Ubuntu 24.04 (libxml 2.9.14), also confirmed on 3v4l.org.
Not reproducible on macOS with libxml 2.9.13.