Commit 873a997d707 for php

commit 873a997d707ae2962a6f7c45eb5f9d3682650374
Author: David Carlier <devnexen@gmail.com>
Date:   Sat Sep 12 14:52:38 2026 +0100

    Fix GH-23637: opcache inheritance cache entry published too early

    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().

    Close GH-23676

diff --git a/NEWS b/NEWS
index 4dedb89b0d3..c70eb74ee53 100644
--- a/NEWS
+++ b/NEWS
@@ -70,6 +70,9 @@ PHP                                                                        NEWS
   . Fixed OSS-Fuzz #5674034779193344 (Read of uninitialized memory in
     is_cacheable_stream_path()). (ndossche)
   . Fix zend_analyze_calls() call_stack buffer overrun. (Mrmaxmeier)
+  . Fixed bug GH-23637 (PHP-FPM worker SIGSEGV: run_time_cache map_ptr offset
+    beyond CG(map_ptr_last) when a class is reached through the CE cache).
+    (David Carlier)

 - PCNTL:
   . Fixed pcntl_signal_dispatch() dropping the queued signals when it runs while
diff --git a/Zend/zend_atomic.h b/Zend/zend_atomic.h
index 56422a721fd..5eeb212ec09 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 8a285928cc0..79c7668ba5e 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();