Skip to content

Dom\XMLDocument::C14N() emits duplicate xmlns declarations after setAttributeNS() on element with default namespace #21548

@veewee

Description

@veewee

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:

  1. Element has a default xmlns (xmlns="...")
  2. Element has at least one non-xmlns attribute (e.g. attr="val")
  3. 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions