CVE-2015-9253: Uncontrolled Resource Consumption (CWE-400)
value: An issue was discovered in PHP 7.3.x before 7.3.0alpha3, 7.2.x before 7.2.8, and before 7.1.20. The php-fpm master process restarts a child process in an endless loop when using program execution functions (e.g., passthru, exec, shell_exec, or system) with a non-blocking STDIN stream, causing this master process to consume 100% of the CPU, and consume disk space with a large volume of error logs, as demonstrated by an attack by a customer of a shared-hosting facility.
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
CVSS v3 base score: 6.5
Published: 2018-02-19T19:29Z
Repository: https://github.com/php/php-src
Language: C
Files changed: NEWS, sapi/fpm/fpm/fpm_children.c, sapi/fpm/fpm/fpm_stdio.c, sapi/fpm/tests/bug73342-nonblocking-stdio.phpt
Commit date: 2018-06-20 12:28:15 +0200
Commit message: Fixed bug #73342

Directly listen on socket, instead of duping it to STDIN and
listening on that.

Diff: diff --git a/NEWS b/NEWS
index 435df031c80..c0e391cb6e2 100644
--- a/NEWS
+++ b/NEWS
@@ -3,10 +3,14 @@ PHP                                                                        NEWS
 ?? ??? ????, PHP 7.1.20
 
 - Date:
   . Fixed bug #76462 (Undefined property: DateInterval::$f). (Anatol)
 
+- FPM:
+  . Fixed bug #73342 (Vulnerability in php-fpm by changing stdin to
+    non-blocking). (Nikita)
+
 22 Jun 2019, PHP 7.1.19
 
 - CLI Server:
   . Fixed bug #76333 (PHP built-in server does not find files if root path
     contains special characters). (Anatol)
diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c
index b48fa54f532..4ee316ba1b9 100644
--- a/sapi/fpm/fpm/fpm_children.c
+++ b/sapi/fpm/fpm/fpm_children.c
@@ -144,10 +144,11 @@ 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)    ||
 	    0 > fpm_status_init_child(wp) ||
 	    0 > fpm_unix_init_child(wp)   ||
diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c
index 40720176e14..76e8b324df6 100644
--- a/sapi/fpm/fpm/fpm_stdio.c
+++ b/sapi/fpm/fpm/fpm_stdio.c
@@ -101,16 +101,10 @@ int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
 		close(fpm_globals.error_log_fd);
 	}
 	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;
 }
 /* }}} */
 
 static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
diff --git a/sapi/fpm/tests/bug73342-nonblocking-stdio.phpt b/sapi/fpm/tests/bug73342-nonblocking-stdio.phpt
new file mode 100644
index 00000000000..3cf44d11ffa
--- /dev/null
+++ b/sapi/fpm/tests/bug73342-nonblocking-stdio.phpt
@@ -0,0 +1,46 @@
+--TEST--
+FPM: bug73342 - Non-blocking stdin
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$code = <<<EOT
+<?php
+echo "Before\n";
+stream_set_blocking(fopen('php://stdin', 'r'), false);
+echo "After\n";
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectBody("Before\nAfter");
+$tester->request()->expectBody("Before\nAfter");
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>

Vulnerable code: 	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;
		}
	}
Fixed code: - FPM:
  . Fixed bug #73342 (Vulnerability in php-fpm by changing stdin to
    non-blocking). (Nikita)

	fpm_globals.listening_socket = dup(wp->listening_socket);
--TEST--
FPM: bug73342 - Non-blocking stdin
--SKIPIF--
<?php include "skipif.inc"; ?>
--FILE--
<?php

require_once "tester.inc";

$cfg = <<<EOT
[global]
error_log = {{FILE:LOG}}
[unconfined]
listen = {{ADDR}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
EOT;

$code = <<<EOT
<?php
echo "Before\n";
stream_set_blocking(fopen('php://stdin', 'r'), false);
echo "After\n";
EOT;

$tester = new FPM\Tester($cfg, $code);
$tester->start();
$tester->expectLogStartNotices();
$tester->request()->expectBody("Before\nAfter");
$tester->request()->expectBody("Before\nAfter");
$tester->terminate();
$tester->expectLogTerminatingNotices();
$tester->close();

?>
Done
--EXPECT--
Done
--CLEAN--
<?php
require_once "tester.inc";
FPM\Tester::clean();
?>