From 966a73f7b7b8373abcbdf7ef3132818187c81a36 Mon Sep 17 00:00:00 2001 From: iloveleon19 <37945260+iloveleon19@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:23:40 +0800 Subject: [PATCH] Fix GH-24319: opcache hash entries can be seen before they are initialised zend_accel_hash_update() fills a new entry and links it into the bucket with plain stores while holding the SHM lock, but zend_accel_hash_find_ex() reads the hash without that lock. On weakly ordered CPUs a reader can reach an entry whose key, indirect or data stores are not visible yet. Publish with a release fence before the bucket store (and before updating data/indirect of an existing entry), and use an acquire fence after loading a non-NULL bucket head in zend_accel_hash_find_ex() and accelerator_get_scripts(). --- ext/opcache/zend_accelerator_hash.c | 13 +++++++++++++ ext/opcache/zend_accelerator_module.c | 7 ++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ext/opcache/zend_accelerator_hash.c b/ext/opcache/zend_accelerator_hash.c index 2fd3dfb5ed56..f2296da397a0 100644 --- a/ext/opcache/zend_accelerator_hash.c +++ b/ext/opcache/zend_accelerator_hash.c @@ -97,6 +97,10 @@ zend_accel_hash_entry* zend_accel_hash_update(zend_accel_hash *accel_hash, zend_ if (entry->hash_value == hash_value && zend_string_equals(entry->key, key)) { + /* Lookups are lock-free: order the initialization of whatever the new + * data points to before the pointer update (writer side only; readers + * rely on the address dependency when following data). */ + ZEND_ATOMIC_FENCE_RELEASE(); if (entry->indirect) { if (indirect_bucket) { entry->data = indirect_bucket; @@ -134,6 +138,10 @@ zend_accel_hash_entry* zend_accel_hash_update(zend_accel_hash *accel_hash, zend_ entry->hash_value = hash_value; entry->key = key; entry->next = accel_hash->hash_table[index]; + /* Lookups are lock-free: the entry must be fully initialized before it + * becomes reachable from the bucket, otherwise a concurrent reader can see + * a matching hash_value with a NULL key (weakly ordered CPUs, e.g. aarch64). */ + ZEND_ATOMIC_FENCE_RELEASE(); accel_hash->hash_table[index] = entry; return entry; } @@ -151,6 +159,11 @@ static zend_always_inline void* zend_accel_hash_find_ex(zend_accel_hash *accel_h index = hash_value % accel_hash->max_num_entries; entry = accel_hash->hash_table[index]; + if (entry) { + /* Order the bucket read before the entry field reads; pairs with the + * release fence before the bucket store in zend_accel_hash_update(). */ + ZEND_ATOMIC_FENCE_ACQUIRE(); + } while (entry) { if (entry->hash_value == hash_value && zend_string_equals(entry->key, key)) { diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 23192e8950bf..78dd658394fb 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -611,7 +611,12 @@ static int accelerator_get_scripts(zval *return_value) array_init(return_value); for (i = 0; inext) { + cache_entry = ZCSG(hash).hash_table[i]; + if (cache_entry) { + /* Lock-free reader, see zend_accel_hash_find_ex(). */ + ZEND_ATOMIC_FENCE_ACQUIRE(); + } + for (; cache_entry; cache_entry = cache_entry->next) { zend_persistent_script *script; char *str; size_t len;