Commit bfbae397a3a for php.net
commit bfbae397a3a0240ab3d690d71f240af66300cbc6
Author: David Carlier <devnexen@gmail.com>
Date: Thu Aug 20 22:22:56 2026 +0100
ext/opcache: opcache.interned_strings_buffer per FPM pool crashed on restart.
Fix #23288
The directive was still accepted once the shared interned string table
had been sized from the master php.ini, so a diverging pool value made
the next restart run accel_interned_strings_restore_state() against a
table that was never allocated. Reject post-startup changes like
opcache.memory_consumption and gate the restore on the shared table
state instead of the per-process directive.
Close GH-23309
diff --git a/NEWS b/NEWS
index a672360f0ae..86b6bd3e2e0 100644
--- a/NEWS
+++ b/NEWS
@@ -31,6 +31,8 @@ PHP NEWS
- Opcache:
. Fixed opcache.protect_memory race under ZTS. (realFlowControl)
+ . Fixed bug GH-23288 (Crash on restart when opcache.interned_strings_buffer
+ is overridden in an individual FPM pool). (David Carlier)
- PDO:
. Fixed a leak when a persistent connection failed a liveness check
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index a2d964c1507..221cc55d9f6 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -2715,7 +2715,7 @@ ZEND_RINIT_FUNCTION(zend_accelerator)
zend_reset_cache_vars();
zend_accel_hash_clean(&ZCSG(hash));
- if (ZCG(accel_directives).interned_strings_buffer) {
+ if (ZCSG(interned_strings).saved_top) {
accel_interned_strings_restore_state();
}
@@ -3444,6 +3444,8 @@ void accel_shutdown(void)
if ((ini_entry = zend_hash_str_find_ptr(EG(ini_directives), "include_path", sizeof("include_path")-1)) != NULL) {
ini_entry->on_modify = orig_include_path_on_modify;
}
+
+ accel_startup_ok = false;
}
void zend_accel_schedule_restart(zend_accel_restart_reason reason)
diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c
index ffa09aaf9e6..23192e8950b 100644
--- a/ext/opcache/zend_accelerator_module.c
+++ b/ext/opcache/zend_accelerator_module.c
@@ -93,6 +93,15 @@ static ZEND_INI_MH(OnUpdateMemoryConsumption)
static ZEND_INI_MH(OnUpdateInternedStringsBuffer)
{
+ if (accel_startup_ok) {
+ if (strcmp(sapi_module.name, "fpm-fcgi") == 0) {
+ zend_accel_error(ACCEL_LOG_WARNING, "opcache.interned_strings_buffer cannot be changed when OPcache is already set up. Are you using php_admin_value[opcache.interned_strings_buffer] in an individual pool's configuration?\n");
+ } else {
+ zend_accel_error(ACCEL_LOG_WARNING, "opcache.interned_strings_buffer cannot be changed when OPcache is already set up.\n");
+ }
+ return FAILURE;
+ }
+
zend_long *p = (zend_long *) ZEND_INI_GET_ADDR();
zend_long size = zend_ini_parse_quantity_warn(new_value, entry->name);
diff --git a/sapi/fpm/tests/gh23288-opcache-interned-strings-buffer-pool.phpt b/sapi/fpm/tests/gh23288-opcache-interned-strings-buffer-pool.phpt
new file mode 100644
index 00000000000..8f48b3a8c20
--- /dev/null
+++ b/sapi/fpm/tests/gh23288-opcache-interned-strings-buffer-pool.phpt
@@ -0,0 +1,50 @@
+--TEST--
+FPM: GH-23288 - opcache.interned_strings_buffer overridden per pool must not crash on restart
+--EXTENSIONS--
+opcache
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = static
+pm.max_children = 1
+php_admin_value[opcache.interned_strings_buffer] = 8
+EOT;
+
+$code = <<<EOT
+<?php
+opcache_reset();
+opcache_reset();
+echo "ok";
+EOT;
+
+$opcache = ini_get('extension_dir') . DIRECTORY_SEPARATOR . 'opcache.' . PHP_SHLIB_SUFFIX;
+$extraArgs = is_file($opcache) ? ['-dzend_extension=' . $opcache] : [];
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start($extraArgs, iniEntries: ['opcache.interned_strings_buffer' => '0']);
+$tester->expectLogStartNotices();
+$tester->request()->expectBody('ok');
+$tester->request()->expectBody('ok');
+$tester->request()->expectBody('ok');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>