From 4b95c0c6a27a4927d080040d22acb989847c6ee9 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Thu, 6 Aug 2026 12:58:34 +0200 Subject: [PATCH 1/2] Fix OPcache memory protection race under ZTS --- ext/opcache/ZendAccelerator.h | 3 +++ ext/opcache/zend_shared_alloc.c | 38 ++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 486074ef0012..991df4efd3c6 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -199,6 +199,9 @@ typedef struct _zend_accel_globals { bool counted; /* the process uses shared memory */ bool enabled; bool locked; /* thread obtained exclusive lock */ +#ifdef ZTS + uint32_t unprotect_depth; +#endif bool accelerator_enabled; /* accelerator enabled for current request */ bool pcre_reseted; zend_accel_directives accel_directives; diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index 8516493dd870..a951edc444a5 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -55,6 +55,11 @@ static const char *g_shared_model; /* pointer to globals allocated in SHM and shared across processes */ ZEND_EXT_API zend_smm_shared_globals *smm_shared_globals; +#ifdef ZTS +static MUTEX_T zts_protect_lock; +static uint32_t zts_unprotected_threads; +#endif + #ifndef ZEND_WIN32 #ifdef ZTS static MUTEX_T zts_lock; @@ -184,6 +189,11 @@ int zend_shared_alloc_startup(size_t requested_size, size_t reserved_size) int res = ALLOC_FAILURE; int i; +#ifdef ZTS + zts_protect_lock = tsrm_mutex_alloc(); + zts_unprotected_threads = 0; +#endif + /* shared_free must be valid before we call zend_shared_alloc() * - make it temporarily point to a local variable */ @@ -338,6 +348,9 @@ void zend_shared_alloc_shutdown(void) tsrm_mutex_free(zts_lock); # endif #endif +#ifdef ZTS + tsrm_mutex_free(zts_protect_lock); +#endif } static size_t zend_shared_alloc_get_largest_free_block(void) @@ -625,13 +638,31 @@ const char *zend_accel_get_shared_model(void) void zend_accel_shared_protect(bool protected) { -#ifdef HAVE_MPROTECT +#if defined(HAVE_MPROTECT) || defined(ZEND_WIN32) int i; if (!smm_shared_globals) { return; } +#ifdef ZTS + /* Memory protection is process-wide, so overlapping writers must be tracked across threads. */ + tsrm_mutex_lock(zts_protect_lock); + if (protected) { + if (ZCG(unprotect_depth) && --ZCG(unprotect_depth) == 0) { + ZEND_ASSERT(zts_unprotected_threads > 0); + zts_unprotected_threads--; + } + if (zts_unprotected_threads) { + tsrm_mutex_unlock(zts_protect_lock); + return; + } + } else if (ZCG(unprotect_depth)++ == 0) { + zts_unprotected_threads++; + } +#endif + +#ifdef HAVE_MPROTECT const int mode = protected ? PROT_READ : PROT_READ|PROT_WRITE; for (i = 0; i < ZSMMG(shared_segments_count); i++) { @@ -653,6 +684,11 @@ void zend_accel_shared_protect(bool protected) } } #endif + +#ifdef ZTS + tsrm_mutex_unlock(zts_protect_lock); +#endif +#endif } bool zend_accel_in_shm(void *ptr) From 400b47939f2ced03aa5d4487b514ce22e309172a Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Thu, 6 Aug 2026 13:47:52 +0200 Subject: [PATCH 2/2] Fix Windows OPcache build --- ext/opcache/zend_shared_alloc.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index a951edc444a5..65d433f7018c 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -669,12 +669,6 @@ void zend_accel_shared_protect(bool protected) mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->end, mode); } #elif defined(ZEND_WIN32) - int i; - - if (!smm_shared_globals) { - return; - } - const int mode = protected ? PAGE_READONLY : PAGE_READWRITE; for (i = 0; i < ZSMMG(shared_segments_count); i++) {