Skip to content

ext/dom: DOMNode::insertBefore($n, $n) drops the node and leaves a self-referencing sibling list #23365

Description

@alexandre-daubois

Description

The following code:

<?php

$document = new DOMDocument();
$root = $document->appendChild($document->createElement('root'));
$text = $root->appendChild($document->createTextNode('self-insertion'));

// Use the same attached DOMText as both the child and insertion reference.
$root->insertBefore($text, $text);

// Reattach the corrupted node so document teardown traverses it.
$root->appendChild($text);

unset($text, $root, $document);

Produces a UAF on libxml2 2.9.14 and double free on libxml2. 2.9.13. insertBefore($n, $n) unlinks $n first, then builds the new position out of $n's parent and sibling fields that the unlink just cleared, so $n ends up outside the document and pointing at itself as its own next sibling.

Asan trace:

==70244==ERROR: AddressSanitizer: attempting double-free on 0x602000002bd0 in thread T0:
    #0 0x000107055258 in free+0x7c (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x41258)
    #1 0x000191105094 in xmlFreeNodeList+0x230 (libxml2.2.dylib:arm64e+0x11094)
    #2 0x000191104a50 in xmlFreeDoc+0xac (libxml2.2.dylib:arm64e+0x10a50)
    #3 0x000104b6da14 in php_libxml_decrement_doc_ref_directly libxml.c:1395
    #4 0x000104b6db28 in php_libxml_decrement_doc_ref libxml.c:1415
    #5 0x000104d5be0c in dom_objects_free_storage php_dom.c:1500

Note that the reproducer is kind of exotic with $root->insertBefore($text, $text);, and another reproducer is available, with less exotic code and its related Asan trace:

<?php

$d = new DOMDocument();
$d->loadXML('<root><p>solo</p></root>');
$p = $d->getElementsByTagName('p')->item(0);
$t = $p->firstChild;
$p->insertBefore($p->firstChild, $p->lastChild);
$p->appendChild($t);

unset($t, $p, $d);

The issue is that $p only has one child, and firstChild === lastChild here

==70241==ERROR: AddressSanitizer: attempting double-free on 0x602000002c10 in thread T0:
    #0 0x000106995258 in free+0x7c (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x41258)
    #1 0x000191105094 in xmlFreeNodeList+0x230 (libxml2.2.dylib:arm64e+0x11094)
    #2 0x000191104a50 in xmlFreeDoc+0xac (libxml2.2.dylib:arm64e+0x10a50)
    #3 0x0001043c5a14 in php_libxml_decrement_doc_ref_directly libxml.c:1395
    #4 0x0001043c5b28 in php_libxml_decrement_doc_ref libxml.c:1415
    #5 0x0001045b3e0c in dom_objects_free_storage php_dom.c:1500
    #6 0x0001051da578 in zend_objects_store_del zend_objects_API.c:193
    #7 0x0001050b1760 in ZEND_UNSET_CV_SPEC_CV_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:102283
    #8 0x000104e857c8 in execute_ex zend_vm_execute.h:110551
    #9 0x000104e85f24 in zend_execute zend_vm_execute.h:115989

PHP Version

All maintained versions

Operating System

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions