[php-src] Issue #24661: FPM: pools with different disable_functions sharing one opcache run scripts wrongly (var_export() calls var_dump(), SIGSEGV in
the restricted pool) on 8.4/8.5/8.6
| From: | Enfiladeguide | Date: | Sat, 24 Oct 2026 18:58:39 +0000 |
| Subject: | [php-src] Issue #24661: FPM: pools with different disable_functions sharing one opcache run scripts wrongly (var_export() calls var_dump(), SIGSEGV in the restricted pool) on 8.4/8.5/8.6 |
||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253164@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24661
Author: Enfiladeguide
### Description
Two pools in **one** FPM master, one with
php_admin_value[disable_functions] and one
without, share the opcache. Once a script has been compiled and cached through one pool, the other
pool executes it wrongly: an internal function call is dispatched to a different internal function,
and a worker of the restricted pool can crash.
fpm.conf:
```ini
[global]
daemonize = no
error_log = /dev/stderr
[a]
user = nobody
listen = /tmp/a.sock
listen.owner = nobody
pm = static
pm.max_children = 1
php_admin_value[disable_functions] = exec
[b]
user = nobody
listen = /tmp/b.sock
listen.owner = nobody
pm = static
pm.max_children = 1
```
t.php:
```php
<?php
echo var_export([1, 'x' => true], true), "\n";
```
Request t.php through pool a, then through pool b (a ~20-line
FastCGI client, fcgi.php, is attached below so that no web server is needed). Resulting
output, php:8.5-fpm (8.5.12), default settings (opcache is built in):
```
pool a: array (
0 => 1,
'x' => true,
)
pool b: array(2) {
[0]=>
int(1)
["x"]=>
bool(true)
}
bool(true)
```
Pool b printed the output of var_dump() (and its return value) instead of
var_export(). Expected output: the same text as pool a. With the order
reversed (b first, then a) pool a, the one with
exec disabled, prints debug_zval_dump() style output (array(2)
interned {...}).
On Ubuntu's 8.5.4 (8.5.4-0ubuntu1.5, -n with
zend_extension=opcache) pool b fails with convert_uudecode() expects
exactly 1 argument, 2 given for the same script, i.e. the call is dispatched to a third
function.
A script that calls the disabled function itself (t2.php: echo exec('echo
hi');), requested through b first and then through a, kills
the worker of the restricted pool:
```
WARNING: [pool a] child 10 exited on signal 11 (SIGSEGV) after 2.015193 seconds from start
```
What I checked:
| | result |
| -- | -- |
| 8.5.12, 8.4.27 (official php:*-fpm images), 8.6.0RC4 (php:8.6-rc-fpm,
ZTS), Ubuntu 8.5.4 | wrong function called |
| 8.3.36 (php:8.3-fpm) | correct |
| opcache.enable=0 | correct |
| opcache.optimization_level=0 | correct |
| 8.4 without zend_extension=opcache (-n) | correct |
| default opcache.optimization_level with the bit 0x20 or the bit
0x40 cleared (one at a time; every other bit alone makes no difference) | correct |
| two separate FPM masters, one pool each, same disable_functions split (8.5.12 and
8.4.27) | correct, no cross-effect |
I did not trace what exactly gets cached or resolved by index or by pointer, so I cannot say more
than that the optimizer passes 0x20 (DFA) and 0x40 (call graph) are both
needed. I saw no case where a disabled function actually ran: the restricted pool crashed or printed
the output of another function, but never executed exec. FPM accepts
php_admin_value[disable_functions] per pool, so a setup like this is not exotic, and
the symptom (wrong output rather than an error) is hard to diagnose; the workaround is
opcache.enable=0, or one master per differing disable_functions set.
I found no open or closed report about this. Related but different: #21974 (SIGSEGV with
disable_functions per pool, ZTS builds only) and #24047 (system id and frameless
functions).
To reproduce (podman/docker; the directory holds fpm.conf, t.php,
t2.php, fcgi.php, run.sh):
```sh
docker run --rm -v "$PWD":/m docker.io/library/php:8.5-fpm sh /m/run.sh "a b a
b" t.php
docker run --rm -v "$PWD":/m docker.io/library/php:8.5-fpm sh /m/run.sh "b a"
t2.php
docker run --rm -v "$PWD":/m docker.io/library/php:8.5-fpm sh /m/run.sh "a b a
b" t.php -d opcache.enable=0 # correct
```
<details><summary>control: one master per pool (correct on 8.5.12 and
8.4.27)</summary>
```sh
sed -n '1,10p' /m/fpm.conf > /tmp/fpm-a.conf
{ sed -n '1,3p' /m/fpm.conf; sed -n '11,16p' /m/fpm.conf; } > /tmp/fpm-b.conf
php-fpm -y /tmp/fpm-a.conf -F >/m/fpm-a.out 2>&1 &
php-fpm -y /tmp/fpm-b.conf -F >/m/fpm-b.out 2>&1 &
sleep 2
for p in a b a b b a; do printf -- "pool %s: " "$p"; php /m/fcgi.php
/tmp/$p.sock /m/t.php 2>&1 | tr '\n' ' '; echo; done
```
</details>
fcgi.php:
```php
<?php
// Minimal FastCGI GET client: php fcgi.php /tmp/a.sock /m/t.php
[$sock, $script] = [$argv[1], $argv[2]];
$rec = fn(int $t, string $b) => pack('CCnnCx', 1, $t, 1, strlen($b), 0) . $b;
$kv = function (string $k, string $v): string {
$l = fn(int $n) => $n < 128 ? chr($n) : pack('N', $n | 0x80000000);
return $l(strlen($k)) . $l(strlen($v)) . $k . $v;
};
$p = $kv('SCRIPT_FILENAME', $script) . $kv('REQUEST_METHOD', 'GET') .
$kv('SCRIPT_NAME', '/t.php')
. $kv('SERVER_PROTOCOL', 'HTTP/1.1') . $kv('REQUEST_URI',
'/t.php') . $kv('QUERY_STRING', '');
$s = stream_socket_client("unix://$sock", $en, $es, 10);
fwrite($s, $rec(1, pack('nCx5', 1, 0)) . $rec(4, $p) . $rec(4, '') . $rec(5,
''));
$out = '';
while (!feof($s)) {
$h = stream_get_contents($s, 8);
if (strlen($h) < 8) break;
$d = unpack('Cv/Ct/nid/nlen/Cpad', $h);
$body = $d['len'] + $d['pad'] ? stream_get_contents($s, $d['len']
+ $d['pad']) : '';
if ($d['t'] === 6 || $d['t'] === 7) $out .= substr($body, 0,
$d['len']);
if ($d['t'] === 3) break;
}
echo preg_replace('~^.*?\r\n\r\n~s', '', $out) ?: "(empty response /
connection reset)\n";
```
run.sh:
```sh
ord=$1; scr=$2; shift 2
php -v | head -1
php-fpm "$@" -y /m/fpm.conf -F >/m/fpm.out 2>&1 &
sleep 2
for p in $ord; do printf -- "pool %s: " "$p"; php /m/fcgi.php /tmp/$p.sock
/m/$scr 2>&1 | tr '\n' ' '; echo; done
grep -E "SIGSEGV|signal" /m/fpm.out
```
### PHP Version
```
PHP 8.5.12 (cli) (built: Oct 21 2026 15:49:51) (NTS) [php:8.5-fpm]
PHP 8.4.27 (cli) (built: Oct 21 2026 15:53:31) (NTS) [php:8.4-fpm, opcache loaded]
PHP 8.6.0RC4 (cli) (built: Oct 21 2026 22:05:36) (ZTS) [php:8.6-rc-fpm]
PHP 8.5.4-0ubuntu1.5 (Ubuntu 26.04)
```
### Operating System
Debian (official Docker images), Ubuntu 26.04
---
*Disclosure: the investigation and this write-up were prepared with automated tooling; the
reproduction was re-run on the official images (8.5.12) immediately before posting.*