[php-src] Issue #23331: ext/dom: UAF node_list_unlink() skips attribute children that still have a live wrapper
[email protected] (alexandre-daubois)
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <[email protected]> |
Issue: https://github.com/php/php-src/issues/23331
Author: alexandre-daubois
### Description
Originally reported by @ExPatch-LLC.
The following code:
```php
<?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
```plain
Happens on master
```
### Operating System
_No response_