-
Notifications
You must be signed in to change notification settings - Fork 8k
Description
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
- Dom\XMLDocument::C14N() drops namespace declarations on DOM-built documents #21544 (C14N drops namespace declarations on DOM-built documents)
- Work around C14N duplicate xmlns bug on libxml 2.9.14 veewee/xml#101 (workaround in veewee/xml)
/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.