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();
+?>