|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2016-10-18 20:02 UTC] xuavis at gmail dot com
Description:
------------
Tested on php 5.6 and 7.0 on both Ubuntu and FreeBSD.
When changing STDIN to non-blocking (see test script), php-fpm child processes start crashing.
This causes rapid restarting of child processes with high cpu load. This can be exploited for denial of service. Also scripts start getting cut off during execution.
Sometimes the master php-fpm process also stops responding to a restart/stop and requires a SIGKILL to stop.
Test script:
---------------
<?php
stream_set_blocking(fopen('php://stdin', 'r'), false);
Expected result:
----------------
strace of child:
write(3, "\1\6\0\1\0S\5\0X-Powered-By: PHP/5.6.27"..., 112) = 112
shutdown(3, SHUT_WR) = 0
recvfrom(3, "\1\5\0\1\0\0\0\0", 8, 0, NULL, NULL) = 8
recvfrom(3, "", 8, 0, NULL, NULL) = 0
close(3) = 0
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
accept(0, strace: Process 19005 detached
clean php-fpm log
Actual result:
--------------
strace of child:
write(3, "\1\6\0\1\0S\5\0X-Powered-By: PHP/5.6.27"..., 112) = 112
shutdown(3, SHUT_WR) = 0
recvfrom(3, "\1\5\0\1\0\0\0\0", 8, 0, NULL, NULL) = 8
recvfrom(3, "", 8, 0, NULL, NULL) = 0
close(3) = 0
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
accept(0, 0x7ffeb7a996c0, 0x7ffeb7a996b0) = -1 EAGAIN (Resource temporarily unavailable)
php-fpm log:
[17-Oct-2016 23:28:49.808690] NOTICE: pid 748, fpm_children_bury(), line 252: [pool www] child 6300 exited with code 0 after 0.001693 seconds from start
[17-Oct-2016 23:28:49.808927] NOTICE: pid 748, fpm_children_make(), line 421: [pool www] child 6301 started
[17-Oct-2016 23:28:49.808946] DEBUG: pid 748, fpm_event_loop(), line 419: event module triggered 1 events
[17-Oct-2016 23:28:49.810581] DEBUG: pid 748, fpm_got_signal(), line 76: received SIGCHLD
[17-Oct-2016 23:28:49.810611] NOTICE: pid 748, fpm_children_bury(), line 252: [pool www] child 6301 exited with code 0 after 0.001690 seconds from start
[17-Oct-2016 23:28:49.810833] NOTICE: pid 748, fpm_children_make(), line 421: [pool www] child 6302 started
[17-Oct-2016 23:28:49.810852] DEBUG: pid 748, fpm_event_loop(), line 419: event module triggered 1 events
[17-Oct-2016 23:28:49.812501] DEBUG: pid 748, fpm_got_signal(), line 76: received SIGCHLD
[17-Oct-2016 23:28:49.812533] NOTICE: pid 748, fpm_children_bury(), line 252: [pool www] child 6302 exited with code 0 after 0.001706 seconds from start
[17-Oct-2016 23:28:49.812750] NOTICE: pid 748, fpm_children_make(), line 421: [pool www] child 6303 started
[17-Oct-2016 23:28:49.812771] DEBUG: pid 748, fpm_event_loop(), line 419: event module triggered 1 events
[17-Oct-2016 23:28:49.814332] DEBUG: pid 748, fpm_got_signal(), line 76: received SIGCHLD
[17-Oct-2016 23:28:49.814362] NOTICE: pid 748, fpm_children_bury(), line 252: [pool www] child 6303 exited with code 0 after 0.001618 seconds from start
[17-Oct-2016 23:28:49.814585] NOTICE: pid 748, fpm_children_make(), line 421: [pool www] child 6304 started
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 19:00:01 2025 UTC |
I think the patch from the first comment only works around the problem ... the real question is this: Why does FPM care about STDIN at all? After some looking around, this seems to be what happens: * wp->listening_socket is what we actually want to listen on. * fpm_globals.listening_socket is always 0 (effectively STDIN), because that's what the global is initialized to. It's never changed. * fpm_run() always returns fpm_globals.listening_socket and that's what fcgi listens on. * to make things line up fpm_stdio_init_child() does a dup2(wp->listening_socket, STDIN_FILENO). So effectively we take the listening socket, dup2 it to STDIN and then listen on STDIN. The following patch removes the indirection through STDIN: diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c index b48fa54..4ee316b 100644 --- a/sapi/fpm/fpm/fpm_children.c +++ b/sapi/fpm/fpm/fpm_children.c @@ -146,6 +146,7 @@ static struct fpm_child_s *fpm_child_find(pid_t pid) /* {{{ */ static void fpm_child_init(struct fpm_worker_pool_s *wp) /* {{{ */ { fpm_globals.max_requests = wp->config->pm_max_requests; + fpm_globals.listening_socket = dup(wp->listening_socket); if (0 > fpm_stdio_init_child(wp) || 0 > fpm_log_init_child(wp) || diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c index 4072017..76e8b32 100644 --- a/sapi/fpm/fpm/fpm_stdio.c +++ b/sapi/fpm/fpm/fpm_stdio.c @@ -103,12 +103,6 @@ int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ fpm_globals.error_log_fd = -1; zlog_set_fd(-1); - if (wp->listening_socket != STDIN_FILENO) { - if (0 > dup2(wp->listening_socket, STDIN_FILENO)) { - zlog(ZLOG_SYSERROR, "failed to init child stdio: dup2()"); - return -1; - } - } return 0; } /* }}} */ This also resolves the issue for me. However, I don't know if this has any side-effects, because something else relies on the STDIN mapping.Sorry, that "&>/dev/null" did not worked out. Using the following method instead of exec, shell_exec, etc. prevents the crashing loop: $command = 'command to execute...'; $pipes = array(); $process = proc_open( $command, array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'), ), $pipes ); stream_set_blocking($pipes[1], true); stream_set_blocking($pipes[2], true); if ( is_resource($process) ) { $output = stream_get_contents($pipes[1]); $error = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); }