From 1a478b14cda428d99f4b9094a877606cb6a881aa Mon Sep 17 00:00:00 2001 From: Muhammad Rizwan Date: Fri, 5 Dec 2025 18:45:15 -0500 Subject: [PATCH 1/3] Fix weakref notify reentrancy during object destruction (GH-20404) --- .../gh20404_weakmap_dtor_reentrancy.phpt | 26 +++++++++++++++++++ Zend/zend_weakrefs.c | 12 ++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/weakrefs/gh20404_weakmap_dtor_reentrancy.phpt diff --git a/Zend/tests/weakrefs/gh20404_weakmap_dtor_reentrancy.phpt b/Zend/tests/weakrefs/gh20404_weakmap_dtor_reentrancy.phpt new file mode 100644 index 0000000000000..fb18c6d9fe5ff --- /dev/null +++ b/Zend/tests/weakrefs/gh20404_weakmap_dtor_reentrancy.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-20404: Crash when adding object to WeakMap during destruction +--FILE-- +r->get(); + for ($i = 0; $i < 8; ++$i) { + $r = new WeakMap; + $r[$o] = 1; + $refs[] = $r; + } + } +}; +$r = WeakReference::create($o); + +echo "END OF SCRIPT\n"; +?> +--EXPECT-- +END OF SCRIPT \ No newline at end of file diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index 83a28808811d5..7ab5c543a48ea 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -125,7 +125,13 @@ static void zend_weakref_register(zend_object *object, void *payload) { static void zend_weakref_unregister(zend_object *object, void *payload, bool weakref_free) { zend_ulong obj_key = zend_object_to_weakref_key(object); void *tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key); + + if (!tagged_ptr) { +#if ZEND_DEBUG ZEND_ASSERT(tagged_ptr && "Weakref not registered?"); +#endif + return; + } void *ptr = ZEND_WEAKREF_GET_PTR(tagged_ptr); uintptr_t tag = ZEND_WEAKREF_GET_TAG(tagged_ptr); @@ -213,13 +219,13 @@ void zend_weakrefs_notify(zend_object *object) { /* Annoyingly we can't use the HT destructor here, because we need access to the key (which * is the object address), which is not provided to the dtor. */ const zend_ulong obj_key = zend_object_to_weakref_key(object); - void *tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key); + void *tagged_ptr; #if ZEND_DEBUG ZEND_ASSERT(tagged_ptr && "Tracking of the IS_OBJ_WEAKLY_REFERENCE flag should be precise"); #endif - if (tagged_ptr) { - zend_weakref_unref(object, tagged_ptr); + while ((tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key))) { zend_hash_index_del(&EG(weakrefs), obj_key); + zend_weakref_unref(object, tagged_ptr); } } From 227f9fef3f9b9e233cc9e1bafa1d96ea0d202f0e Mon Sep 17 00:00:00 2001 From: Muhammad Rizwan Date: Fri, 5 Dec 2025 18:54:22 -0500 Subject: [PATCH 2/3] FIx: Initialize tagged_ptr in zend_weakrefs_notify --- Zend/zend_weakrefs.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index 7ab5c543a48ea..a7d74935885d3 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -219,13 +219,14 @@ void zend_weakrefs_notify(zend_object *object) { /* Annoyingly we can't use the HT destructor here, because we need access to the key (which * is the object address), which is not provided to the dtor. */ const zend_ulong obj_key = zend_object_to_weakref_key(object); - void *tagged_ptr; + void *tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key); #if ZEND_DEBUG ZEND_ASSERT(tagged_ptr && "Tracking of the IS_OBJ_WEAKLY_REFERENCE flag should be precise"); #endif - while ((tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key))) { + while (tagged_ptr) { zend_hash_index_del(&EG(weakrefs), obj_key); zend_weakref_unref(object, tagged_ptr); + tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key); } } From df8f88eedb68699cc717640e797b73a4c842ecbf Mon Sep 17 00:00:00 2001 From: Muhammad Rizwan Date: Fri, 5 Dec 2025 19:28:40 -0500 Subject: [PATCH 3/3] Handle missing weakref entries in zend_weakref_unregister --- Zend/zend_weakrefs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index a7d74935885d3..b699a8d617d6a 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -128,7 +128,7 @@ static void zend_weakref_unregister(zend_object *object, void *payload, bool wea if (!tagged_ptr) { #if ZEND_DEBUG - ZEND_ASSERT(tagged_ptr && "Weakref not registered?"); + ZEND_ASSERT("Weakref not registered?"); #endif return; }