[php-src] Issue #20405: Accessing the object after WeakRef unreferencing results in use-after-free

From: Date: Thu, 06 Nov 2025 13:42:11 +0000
Subject: [php-src] Issue #20405: Accessing the object after WeakRef unreferencing results in use-after-free
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-251185@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/20405 Author: bwoebi ### Description The following code: ```php <?php $w = new WeakMap; $o = new stdClass; $w[$o] = new class($r) { function __construct(public &$r) {} function __destruct() { $o = $this->r->get(); register_shutdown_function(function() use ($o) { var_dump($o); }); } }; $r = WeakReference::create($o); ``` Resulted in this output: ``` ==36789== Invalid read of size 4 ==36789== at 0x54C4404: zend_gc_delref (zend_types.h:1342) ==36789== by 0x54BFAC7: i_zval_ptr_dtor (zend_variables.h:43) ==36789== by 0x54BF86B: zend_array_destroy (/usr/local/src/php/Zend/zend_hash.c:1840) ==36789== by 0x54862CF: zend_destroy_static_vars (/usr/local/src/php/Zend/zend_opcode.c:532) ==36789== by 0x55C4DE7: zend_closure_free_storage (/usr/local/src/php/Zend/zend_closures.c:522) ==36789== by 0xBA0918F: dd_uhook_closure_free_wrapper (uhook.c:1158) ==36789== by 0x55EA76F: zend_objects_store_del (/usr/local/src/php/Zend/zend_objects_API.c:200) ==36789== by 0x5499757: rc_dtor_func (/usr/local/src/php/Zend/zend_variables.c:57) ==36789== by 0x54997F3: i_zval_ptr_dtor (zend_variables.h:44) ==36789== by 0x5499793: zval_ptr_dtor (/usr/local/src/php/Zend/zend_variables.c:84) ==36789== by 0x52333E3: fci_release (/usr/local/src/php/ext/standard/basic_functions.c:1611) ==36789== by 0x522B973: user_shutdown_function_dtor (/usr/local/src/php/ext/standard/basic_functions.c:1622) ==36789== Address 0xe813150 is 0 bytes inside a block of size 40 free'd ==36789== at 0x604B614: free (m_replacemalloc/vg_replace_malloc.c:989) ==36789== by 0x544D673: _efree_custom (/usr/local/src/php/Zend/zend_alloc.c:2500) ==36789== by 0x544D5C7: _efree (/usr/local/src/php/Zend/zend_alloc.c:2620) ==36789== by 0x55EA7DF: zend_objects_store_del (/usr/local/src/php/Zend/zend_objects_API.c:204) ==36789== by 0x5499757: rc_dtor_func (/usr/local/src/php/Zend/zend_variables.c:57) ==36789== by 0x54997F3: i_zval_ptr_dtor (zend_variables.h:44) ==36789== by 0x5499793: zval_ptr_dtor (/usr/local/src/php/Zend/zend_variables.c:84) ==36789== by 0x54BE46F: _zend_hash_del_el_ex (/usr/local/src/php/Zend/zend_hash.c:1488) ==36789== by 0x54BDF6B: _zend_hash_del_el (/usr/local/src/php/Zend/zend_hash.c:1515) ==36789== by 0x54C15C3: zend_hash_reverse_apply (/usr/local/src/php/Zend/zend_hash.c:2231) ==36789== by 0x547A843: shutdown_destructors (/usr/local/src/php/Zend/zend_execute_API.c:260) ==36789== by 0x549C227: zend_call_destructors (/usr/local/src/php/Zend/zend.c:1281) ==36789== Block was alloc'd at ==36789== at 0x60485D4: malloc (m_replacemalloc/vg_replace_malloc.c:446) ==36789== by 0x544DA1B: __zend_malloc (/usr/local/src/php/Zend/zend_alloc.c:3128) ==36789== by 0x544D54F: _malloc_custom (/usr/local/src/php/Zend/zend_alloc.c:2491) ==36789== by 0x544D493: _emalloc (/usr/local/src/php/Zend/zend_alloc.c:2610) ==36789== by 0x55E0907: zend_objects_new (/usr/local/src/php/Zend/zend_objects.c:189) ==36789== by 0x54A6143: _object_and_properties_init (/usr/local/src/php/Zend/zend_API.c:1773) ==36789== by 0x54A621B: object_init_ex (/usr/local/src/php/Zend/zend_API.c:1796) ==36789== by 0x552F62F: ZEND_NEW_SPEC_CONST_UNUSED_HANDLER (zend_vm_execute.h:10557) ==36789== by 0x54E2073: execute_ex (zend_vm_execute.h:57037) ==36789== by 0x54E2483: zend_execute (zend_vm_execute.h:61634) ==36789== by 0x549E34B: zend_execute_scripts (/usr/local/src/php/Zend/zend.c:1895) ==36789== by 0x53CB8E3: php_execute_script (/usr/local/src/php/main/main.c:2529) ``` Not exactly sure what to expect - either we just decide to make WeakReference->get() return NULL when the targeted object has IS_OBJ_FREE_CALLED. Which would fix #20404 trivially as well. But it would be a BC break and render the object inaccessible in "externally associated destructors" (I mean dummy classes with destructors in weakmaps to observe an object to be freed). Or we strip the IS_OBJ_FREE_CALLED flag in the case the refcount is increased again in zend_object_std_dtor and skip freeing the object in case the flag is no longer present after the free_obj() call. The latter solution is more flexible and does not have a BC break. ### PHP Version ```plain PHP 8.2+ ``` ### Operating System _No response_

« previous php.bugs (#251185) next »