Skip to content

UAF in custom XPath function #22077

@afflerbach

Description

@afflerbach

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

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions