[php-src] PR #24766: Fix GH-24661: FPM pools with different disable_functions share cached scripts
| From: | Enfiladeguide | Date: | Mon, 26 Oct 2026 23:11:16 +0000 |
| Subject: | [php-src] PR #24766: Fix GH-24661: FPM pools with different disable_functions share cached scripts | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39944@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24766
Author: Enfiladeguide
Fixes GH-24661.
*Quoted text written with an LLM and checked by a human developer.*
> This follows the suggestion in GH-24661 to fix the FPM case with a mechanism similar to #24664.
>
> FPM applies a pool's
disable_functions and PåÞ=¶Ü<·ãÖ~oÐ
> Ýextension in the worker processes (¼�–«ùF$¶
> ©F7žfpm_php_init_child()), after startup, while all pools of a master share one
> opcache. A script compiled in one pool and run in another depends on the internal functions and
> extensions it was compiled with: ZEND_INIT_FCALL offsets pick the
> wrong function, a call to a function that the pool disables fails the assertion in
> ZEND_INIT_FCALL (a NULL dereference in release builds), with
> opcache.jit=function the disabled function is still called, and
> constants of an extension loaded by one pool are substituted in scripts that the other pool runs. A
> run-time check of the offsets would not be enough, since the optimizer also folds Æ|Ý6;òq
> &ö'¾function_exists() and the JIT embeds the function.
>
> With this patch, opcache checks in RINIT whether the internal functions or extensions differ
> from those at startup. If they do, the process keeps its scripts apart from processes that differ:
>
> - in shared memory, by mixing a hash of them into root_hash, as
> for chroot;
> - in the file cache, by using a system ID derived from
> zend_system_id and that hash.
>
> After startup, functions are only removed (disable_functions)
> or added with an extension, so the check in RINIT compares just the numbers of functions and
> extensions. Processes that match the startup state use the cache as before, and pools with the same
> functions and extensions share their entries.
>
> The file cache needs this too. Since the fix for GH-17733 (#24664), the
> INIT_FCALL offsets are no longer zeroed there, and the system ID
> covers the function table. But the system ID is finalized in
> php_module_startup() of the master, before a pool applies its
> disable_functions, so pools with
> opcache.file_cache_only=1 read each other's offsets. With this
> patch they use different directories.
>
> Limitations:
>
> - With opcache.preload, nothing changes. Preloaded scripts are
> stored at startup, so a pool with a separate namespace would not find them, and including a
> preloaded file would fail with "Cannot redeclare function". They are also compiled with
> the functions of the master in any case.
> - opcache_invalidate() only invalidates the entry of the
> calling pool, as with chroot. opcache_reset() is not affected.
> - Pools that differ each cache their own copy of a script.
>
> Tests: three FPM tests, for two pools with different
> disable_functions (shared memory and file cache, both request
> orders), disable_functions in
> PHP_ADMIN_VALUE, and a pool that loads an extension and disables as
> many functions as it adds. They fail on PHP-8.4 and pass with this patch, also on PHP-8.5, PHP-8.6
> and master. With opcache in shared memory and file cache modes, with JIT, with preload, and in a ZTS
> build with ASAN, no other test fails that does not fail without the patch.