[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_
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.