mirror of
https://github.com/php/php-src.git
synced 2026-10-28 17:56:50 +08:00
Fix GH-24046: FPM worker stdio pipe fd closed twice
When a worker exits while one of its child processes still holds the worker's stdout/stderr pipe open, the drain in fpm_child_close() gets EAGAIN instead of EOF and leaves the fd open. fpm_child_close() then closed the fd but kept its number in the child struct, so the postponed free closed it (and removed its event) a second time. If a new worker had been given the same fd number in the meantime, its pipe was closed and every later write it made to stdio failed with EPIPE. Remove the event and reset the fd to -1 when closing it in fpm_child_close() so the postponed free cannot act on it again. Closes GH-24056. Co-authored-by: ndossche <7771979+ndossche@users.noreply.github.com>
This commit is contained in:
1 parent
98043f3a1c
commit
cd161267c0
3 files changed
+68
No files matched your search
@@ -75,6 +75,9 @@ PHP NEWS
|
||||
. Fixed crash and stack memory disclosure in the worker output logging with
|
||||
a pool name longer than the message prefix buffer or log_limit.
|
||||
(Martin Mirchev)
|
||||
. Fixed bug GH-24046 (FPM worker stdio pipe fd closed twice when a child
|
||||
process outlives the worker, breaking a later worker's stdio).
|
||||
(Ian P. Christian, ndossche)
|
||||
|
||||
- Fileinfo:
|
||||
. Fixed bug GH-24289 (finfo::file() reports the wrong argument number for
|
||||
|
||||
@@ -87,7 +87,11 @@ static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{
|
||||
fpm_event_fire(&child->ev_stdout);
|
||||
}
|
||||
if (child->fd_stdout != -1) {
|
||||
if (in_event_loop) {
|
||||
fpm_event_del(&child->ev_stdout);
|
||||
}
|
||||
close(child->fd_stdout);
|
||||
child->fd_stdout = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +101,11 @@ static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{
|
||||
fpm_event_fire(&child->ev_stderr);
|
||||
}
|
||||
if (child->fd_stderr != -1) {
|
||||
if (in_event_loop) {
|
||||
fpm_event_del(&child->ev_stderr);
|
||||
}
|
||||
close(child->fd_stderr);
|
||||
child->fd_stderr = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
--TEST--
|
||||
GH-24046 (FPM: worker stdio pipe fd closed twice when a child process outlives the worker)
|
||||
--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
|
||||
pm.max_requests = 1
|
||||
catch_workers_output = yes
|
||||
decorate_workers_output = no
|
||||
EOT;
|
||||
|
||||
$code = <<<'EOT'
|
||||
<?php
|
||||
/* dup() of the worker's stderr pipe that is inherited by the background process */
|
||||
$stderr = fopen('php://stderr', 'a');
|
||||
if (@fwrite($stderr, "log line\n") === false) {
|
||||
echo "write to stderr failed";
|
||||
exit;
|
||||
}
|
||||
if (isset($_GET['bg'])) {
|
||||
exec('sleep 3 > /dev/null 2>&1 &');
|
||||
}
|
||||
echo "ok";
|
||||
EOT;
|
||||
|
||||
$tester = new FPM\Tester($cfg, $code);
|
||||
$tester->start();
|
||||
$tester->expectLogStartNotices();
|
||||
/* The worker exits after this request while the background process still holds
|
||||
* its stderr pipe open. The replacement worker gets the same pipe fd numbers. */
|
||||
$tester->request('bg=1')->expectBody('ok');
|
||||
/* Wait for the postponed free of the exited worker. */
|
||||
usleep(1500000);
|
||||
$tester->request()->expectBody('ok');
|
||||
$tester->terminate();
|
||||
$tester->expectNoLogPattern('/unable to (remove|read)/');
|
||||
$tester->close();
|
||||
|
||||
?>
|
||||
Done
|
||||
--EXPECT--
|
||||
Done
|
||||
--CLEAN--
|
||||
<?php
|
||||
require_once "tester.inc";
|
||||
FPM\Tester::clean();
|
||||
?>
|
||||
Reference in new issue
Block a user