|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
[2016-11-16 04:29 UTC] webmaster_20161114 at cubiclesoft dot com
[2016-11-16 06:11 UTC] stas@php.net
-Status: Open
+Status: Feedback
[2016-11-16 06:11 UTC] stas@php.net
[2016-11-16 09:37 UTC] webmaster_20161114 at cubiclesoft dot com
-Status: Feedback
+Status: Open
[2016-11-16 09:37 UTC] webmaster_20161114 at cubiclesoft dot com
[2017-09-07 16:17 UTC] cmb@php.net
[2018-01-20 14:43 UTC] webmaster_20161114 at cubiclesoft dot com
[2018-01-22 20:57 UTC] stas@php.net
-Type: Security
+Type: Bug
[2018-07-10 15:07 UTC] bwoebi@php.net
-Type: Bug
+Type: Documentation Problem
[2018-07-10 15:07 UTC] bwoebi@php.net
[2018-07-20 15:32 UTC] webmaster_20161114 at cubiclesoft dot com
-Type: Documentation Problem
+Type: Bug
[2018-07-20 15:32 UTC] webmaster_20161114 at cubiclesoft dot com
[2019-07-18 14:33 UTC] nikic@php.net
[2019-07-22 19:13 UTC] nikic@php.net
-Status: Open
+Status: Closed
-Assigned To:
+Assigned To: nikic
[2019-07-22 19:13 UTC] nikic@php.net
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Fri Nov 07 08:00:02 2025 UTC |
Description: ------------ Premature socket termination from one side and then using fwrite() to send more data from the other side than the current maximum TCP packet size causes php_sockop_write() to emit a PHP Notice about a broken connection/pipe and return 0. This, in turn, will lead to an infinite loop if fwrite() is used as seen in the documentation for fwrite(). Many implementations have utilized the PHP documentation's fwrite_stream() userland function in various forms for TCP/IP socket communications. Attempting to detect network failure using other common functions does not work: stream_select() immediately returns and indicates that the socket as both readable and writable ($except array, if passed, is empty) and feof() returns false. Attempting to read a broken socket to trigger a change in a feof() call is not always an option. An attacker has multiple vectors to maliciously use this knowledge to perform a Denial of Service attack, especially if they can command a host to communicate with a server that they control. For example, an attacker could submit a very long URL of an image where the target host will then use a PHP userland library to go retrieve the URL using fwrite() in a loop to send all of the data. Upon connecting and optionally reading a small amount of the input, the attacker's server prematurely terminates the request. The PHP script then enters an infinite loop, occupying one CPU core until the script is terminated (e.g. timeout). The situation can also arise due to normal network and software conditions (e.g. an Apache configuration might not allow a large payload for the request line). Assuming that a return value of 0 from fwrite() is equal to an underlying network failure is not a valid solution for non-blocking and, to a lesser extent, blocking sockets. In addition, it is currently extremely difficult to differentiate failure vs. a temporary condition (e.g. an internal buffer is still full or a slightly broken blocking sockets implementation) vs. attempting to write 0 bytes since 0 is returned in all three cases. The latter case being rather minor while the former two are vital for non-blocking sockets to function properly. The offending lines of code that are most concerning are these in php_sockop_write(): if (didwrite < 0) { didwrite = 0; } A possible fix could be to modify php_sockop_write(): - if (didwrite < 0) { - didwrite = 0; - } And then modify fwrite(): ret = php_stream_write(stream, input, num_bytes); + if (ret < 0) { + RETURN_FALSE; + } RETURN_LONG(ret); To keep BC breaks to a minimum and correctly return false on socket write errors. Doing the above may also correct other write error issues as well, standardizing on false as a fwrite() $handle error condition. There may be a similar problem lurking in OpenSSL enabled sockets with fwrite() in a loop. I am in the process of requesting a CVE ID from MITRE for this vulnerability. Test script: --------------- Server: <?php $fp = stream_socket_server("tcp://127.0.0.1:10000", $errornum, $errorstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); do { if (($fp2 = stream_socket_accept($fp)) !== false) { echo fread($fp2, 100000) . "\n"; $str = str_repeat("S", 4096); fwrite($fp2, $str); sleep(3); exit(); } } while (1); ?> Client: <?php $fp = stream_socket_client("tcp://127.0.0.1:10000", $errornum, $errorstr, 10, STREAM_CLIENT_CONNECT); // The following function was taken verbatim from the official example for fwrite(): http://php.net/manual/en/function.fwrite.php function fwrite_stream($fp, $string) { for ($written = 0; $written < strlen($string); $written += $fwrite) { $fwrite = fwrite($fp, substr($string, $written)); if ($fwrite === false) { return $written; } } return $written; } // Set up the broken connection. $str = str_repeat("C", 100000); fwrite_stream($fp, $str); echo fread($fp, 4096) . "\n"; sleep(6); // Connection is broken here. Infinite loop in fwrite_stream(). fwrite() emits PHP Notices and the process has high CPU usage. fwrite_stream($fp, $str); ?> Expected result: ---------------- fwrite() should return false and the example client should exit. Actual result: -------------- fwrite() returns 0 after the connection breaks. As a result, the client enters into an infinite loop, each call to fwrite() emitting a line similar to this one: PHP Notice: fwrite(): send of 8192 bytes failed with errno=32 Broken pipe in /home/testuser/bug_client.php on line 7 Also, the CPU of one core is consumed until the PHP script is manually terminated.