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
Operating System
No response
Description
Originally reported by @ExPatch-LLC.
The following code:
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)zeroesnode->next, so the loop advancenode = node->nextreadsNULLand stops;breakonXML_ENTITY_REF_NODEleaves 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
Operating System
No response