Description
Registering a custom XPath function that provides nodes from a newly created document may result in a heap-use-after-free in request shutdown.
This happens, when the nodes from the new document are accessed further down the road and their owner document has already been freed:
The following code:
<?php
$document = new DOMDocument;
$xpath = new DOMXPath($document);
$xpath->registerNamespace("my", "my.ns");
$xpath->registerPHPFunctionNS('my.ns', 'include', function(): DOMElement {
$includedDocument = new DOMDocument;
$includedDocument->loadXML('<root><uaf/><node/><uaf/></root>');
return $includedDocument->documentElement;
});
$nodeset = $xpath->query('my:include()/uaf');
$node = $nodeset->item(0);
var_dump($node->ownerDocument->saveXML());
Resulted in this output when run by PHP 8.5 built with -fsanitize=address:
==790922==ERROR: AddressSanitizer: heap-use-after-free on address 0x7c000e9e94c8 at pc 0x55e5398509dc bp 0x7ffc76973780 sp 0x7ffc76973770
READ of size 4 at 0x7c000e9e94c8 thread T0
#0 0x55e5398509db in dom_objects_free_storage /php-src/ext/dom/php_dom.c:1488
#1 0x55e53a846eb2 in zend_objects_store_del /php-src/Zend/zend_objects_API.c:196
#2 0x55e53a897b8c in rc_dtor_func /php-src/Zend/zend_variables.c:57
#3 0x55e53a897a5c in i_zval_ptr_dtor /php-src/Zend/zend_variables.h:45
#4 0x55e53a897e56 in zval_ptr_dtor /php-src/Zend/zend_variables.c:84
#5 0x55e53a77b43a in _zend_hash_del_el_ex /php-src/Zend/zend_hash.c:1493
#6 0x55e53a77b75c in _zend_hash_del_el /php-src/Zend/zend_hash.c:1520
#7 0x55e53a780ece in zend_hash_reverse_apply /php-src/Zend/zend_hash.c:2236
#8 0x55e53a58f4c9 in shutdown_destructors /php-src/Zend/zend_execute_API.c:260
#9 0x55e53a8adaae in zend_call_destructors /php-src/Zend/zend.c:1336
#10 0x55e53a2dfea7 in php_request_shutdown /php-src/main/main.c:1981
#11 0x55e53a8ba492 in do_cli /php-src/sapi/cli/php_cli.c:1170
#12 0x55e53a8bb076 in main /php-src/sapi/cli/php_cli.c:1374
#13 0x7f4012027c8d (/usr/lib/libc.so.6+0x27c8d) (BuildId: da90c940060d13f3bc8a337f9c591b40ca12815e)
#14 0x7f4012027dca in __libc_start_main (/usr/lib/libc.so.6+0x27dca) (BuildId: da90c940060d13f3bc8a337f9c591b40ca12815e)
#15 0x55e539604164 in _start (/usr/local/bin/php+0x604164) (BuildId: 5770d9d76c15259c4b77c2228efebb9e62362df1)
0x7c000e9e94c8 is located 8 bytes inside of 120-byte region [0x7c000e9e94c0,0x7c000e9e9538)
freed by thread T0 here:
#0 0x7f401295eda1 (/usr/lib/libasan.so.8+0x15eda1) (BuildId: 07514fafdd549d834cdd73d686195a09c5bbd488)
#1 0x7f40125dff1d in xmlFreeNodeList (/usr/lib/libxml2.so.16+0x4ff1d) (BuildId: 0dbd595f36837b730cc379d6319057feab1b37bc)
previously allocated by thread T0 here:
#0 0x7f4012960181 in malloc (/usr/lib/libasan.so.8+0x160181) (BuildId: 07514fafdd549d834cdd73d686195a09c5bbd488)
#1 0x7f40125e3257 in xmlNewDocNodeEatName (/usr/lib/libxml2.so.16+0x53257) (BuildId: 0dbd595f36837b730cc379d6319057feab1b37bc)
#2 0x7f40125e3a19 in xmlSAX2StartElementNs (/usr/lib/libxml2.so.16+0x53a19) (BuildId: 0dbd595f36837b730cc379d6319057feab1b37bc)
#3 0x7f40125cd0e2 (/usr/lib/libxml2.so.16+0x3d0e2) (BuildId: 0dbd595f36837b730cc379d6319057feab1b37bc)
SUMMARY: AddressSanitizer: heap-use-after-free /php-src/ext/dom/php_dom.c:1488 in dom_objects_free_storage
But I expected this output instead:
<?xml version="1.0"?>
<root><uaf/><node/><uaf/></root>
PHP Version
PHP 8.5.6 (cli) (built: May 7 2026 19:09:38) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.6, Copyright (c) Zend Technologies
with Zend OPcache v8.5.6, Copyright (c), by Zend Technologies
Operating System
No response
Description
Registering a custom XPath function that provides nodes from a newly created document may result in a heap-use-after-free in request shutdown.
This happens, when the nodes from the new document are accessed further down the road and their owner document has already been freed:
The following code:
Resulted in this output when run by PHP 8.5 built with
-fsanitize=address:But I expected this output instead:
PHP Version
Operating System
No response