Repository navigation
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 #24661
Description
Description
Quoted text written with an LLM and checked by a human developer.
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:
[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 = 1t.php:
<?php
echo var_export([1, 'x' => true], true), "\n";Request
t.phpthrough poola, then through poolb(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
bprinted the output ofvar_dump()(and its return value) instead ofvar_export(). Expected output: the same text as poola. With the order reversed (bfirst, thena) poola, the one withexecdisabled, printsdebug_zval_dump()style output (array(2) interned {...}).
On Ubuntu's 8.5.4 (
8.5.4-0ubuntu1.5,-nwithzend_extension=opcache) poolbfails withconvert_uudecode() expects exactly 1 argument, 2 givenfor 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 throughbfirst and then througha, 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:*-fpmimages), 8.6.0RC4 (php:8.6-rc-fpm, ZTS), Ubuntu 8.5.4wrong function called 8.3.36 ( php:8.3-fpm)correct opcache.enable=0correct opcache.optimization_level=0correct 8.4 without zend_extension=opcache(-n)correct default opcache.optimization_levelwith the bit0x20or the bit0x40cleared (one at a time; every other bit alone makes no difference)correct two separate FPM masters, one pool each, same disable_functionssplit (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) and0x40(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 executedexec. FPM acceptsphp_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 isopcache.enable=0, or one master per differingdisable_functionsset.
I found no open or closed report about this.Same root cause as GH-17733. Related but different: #21974 (SIGSEGV withdisable_functionsper 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):
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 # correctcontrol: one master per pool (correct on 8.5.12 and 8.4.27)
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; donefcgi.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:
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.outPHP 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
Activity
ndossche commentedon Oct 24, 2026
Just based on my gut feeling, this is probably related to #17733
Enfiladeguide commentedon Oct 25, 2026
Quoted text written with an LLM and checked by a human developer.
ndossche commentedon Oct 25, 2026
Thanks, I won't merge the issues, since I think it's useful to keep the fpm one separate from the file cache one. Perhaps fpm van be fixed using a similar mechanism.
Fix phpGH-24661: FPM pools with different disable_functions share cac…