Skip to content

ext/dom: UAF node_list_unlink() skips attribute children that still have a live wrapper #23331

Description

@alexandre-daubois

Description

Originally reported by @ExPatch-LLC.

The following code:

<?php

$doc = new DOMDocument();
$doc->loadXML('<!DOCTYPE root [<!ENTITY e "X">]><root attr="a&e;b"/>');
$attr = $doc->documentElement->getAttributeNode('attr');
$child = $attr->lastChild;
$doc->documentElement->setAttribute('attr', 'updated');
var_dump($child->nodeType);

Produces an UAF.

node_list_unlink() is meant to detach an attribute's children that still have a live PHP wrapper before libxml2 frees them, but it has two exits that abandon the remaining siblings:

  • xmlUnlinkNode(node) zeroes node->next, so the loop advance node = node->next reads NULL and stops;
  • the break on XML_ENTITY_REF_NODE leaves the loop entirely rather than just skipping recursion into the entity reference.

Any child past either exit is then freed by xmlSetProp() / xmlSetNsProp() / xmlFreeProp() while its PHP wrapper stays alive, so the wrapper reads freed memory.

PHP Version

Happens on master

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