From f752a7bbac8858e86a52d8117f9ba897d8f48cf7 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 12 Sep 2026 14:52:38 +0100 Subject: [PATCH] ext/opcache: inheritance cache entry published before ZCSG(map_ptr_last). Fix #23637 zend_accel_inheritance_cache_add() linked the new entry into proto->inheritance_cache before updating ZCSG(map_ptr_last), so another process could take that entry in zend_accel_inheritance_cache_get(), extend its map_ptr table only up to the previous value, and cache a class whose methods' run_time_cache offsets sit past its own CG(map_ptr_last). Calling such a method read an uninitialised slot, kept by zend_init_func_run_time_cache() as it is not NULL, which an fcall observer then dereferenced. The entry is now published last. The store order alone does not hold on weakly ordered architectures, hence the fences. The acquire sits right after ce->inheritance_cache is read, not before the ZCSG(map_ptr_last) test: it pairs with the release only when sequenced after the load reading the published pointer, and it also has to cover the entry walk in zend_accel_inheritance_cache_find(). --- Zend/zend_atomic.h | 17 +++++++++++++++++ ext/opcache/ZendAccelerator.c | 7 ++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Zend/zend_atomic.h b/Zend/zend_atomic.h index 56422a721fdb..5eeb212ec09e 100644 --- a/Zend/zend_atomic.h +++ b/Zend/zend_atomic.h @@ -385,6 +385,23 @@ ZEND_API bool zend_atomic_bool_load(const zend_atomic_bool *obj); ZEND_API int zend_atomic_int_load(const zend_atomic_int *obj); #endif +#if defined(HAVE_C11_ATOMICS) +# define ZEND_ATOMIC_FENCE_RELEASE() __c11_atomic_thread_fence(__ATOMIC_RELEASE) +# define ZEND_ATOMIC_FENCE_ACQUIRE() __c11_atomic_thread_fence(__ATOMIC_ACQUIRE) +#elif defined(HAVE_GNUC_ATOMICS) +# define ZEND_ATOMIC_FENCE_RELEASE() __atomic_thread_fence(__ATOMIC_RELEASE) +# define ZEND_ATOMIC_FENCE_ACQUIRE() __atomic_thread_fence(__ATOMIC_ACQUIRE) +#elif defined(HAVE_SYNC_ATOMICS) +# define ZEND_ATOMIC_FENCE_RELEASE() __sync_synchronize() +# define ZEND_ATOMIC_FENCE_ACQUIRE() __sync_synchronize() +#elif defined(ZEND_WIN32) +# define ZEND_ATOMIC_FENCE_RELEASE() MemoryBarrier() +# define ZEND_ATOMIC_FENCE_ACQUIRE() MemoryBarrier() +#else +# define ZEND_ATOMIC_FENCE_RELEASE() ((void)0) +# define ZEND_ATOMIC_FENCE_ACQUIRE() ((void)0) +#endif + END_EXTERN_C() #endif diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 221cc55d9f6d..cf54454afbb7 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2302,6 +2302,10 @@ static zend_class_entry* zend_accel_inheritance_cache_get(zend_class_entry *ce, bool needs_autoload; zend_inheritance_cache_entry *entry = ce->inheritance_cache; + if (entry) { + ZEND_ATOMIC_FENCE_ACQUIRE(); + } + while (entry) { entry = zend_accel_inheritance_cache_find(entry, ce, parent, traits_and_interfaces, &needs_autoload); if (entry) { @@ -2458,12 +2462,13 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce, entry->num_warnings = EG(num_errors); entry->warnings = zend_persist_warnings(EG(num_errors), EG(errors)); entry->next = proto->inheritance_cache; - proto->inheritance_cache = entry; EG(num_errors) = 0; EG(errors) = NULL; ZCSG(map_ptr_last) = CG(map_ptr_last); + ZEND_ATOMIC_FENCE_RELEASE(); + proto->inheritance_cache = entry; zend_shared_alloc_destroy_xlat_table();