php/php-src — pull request #966 by redfoxli (2014-12-26 12:54:34+00:00)
Title: avoid a php server attack method base fin loss

problem
=======

I found **a php server attack method base fin loss**
The socket which talk with client is block, don't set any timeout，and **write a dead loop to receive the fin**
The refer code is here:
```cpp
void fcgi_close(fcgi_request *req, int force, int destroy)
{
#ifdef _WIN32
...........
#endif

if ((force || !req->keep) && req->fd >= 0) {
#ifdef _WIN32
...........
while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} ///hang here if client don't send the pack of fin
............
#else
if (!force) {
char buf[8];
shutdown(req->fd, 1);
while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} ///hang here if client don't send the pack of fin
}
close(req->fd);
#endif
req->fd = -1;
fpm_request_finished();
}
}
````
The attack method is very easy. We only know the ip && port of php server。
**We send a request to php server base fastcgi protocol, and don't close the socket, then one worker will hang.**
We continued to send such a request， all worker will hang,
It can make service unavailable.
The attack code is here:
````php
'/home/file_not_exist.php',
'REQUEST_METHOD' => 'GET',
'CONTENT_LENGTH' => strlen($content_body),
);

send_request($socket[$cnt], $content_body, $env, $fcgi_request_id);
$result = get_response($socket[$cnt]);
echo "***************$cnt*****************\n";
var_dump($result);
//socket_close($socket[$cnt]);//we don't send FIN to server
}

//can't run here
for($i = 1; $i <= $cnt; ++$i)
{
socket_close($socket[$i]);
}
}
````

The stack of hang php is here:
````cpp
(gdb) bt
#0 0x0000003f0b90b4e4 in recv () from /lib64/tls/libpthread.so.0
#1 0x000000000098ed78 in fcgi_close (req=0x7fffa8c58ad0, force=0, destroy=1) at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fastcgi.c:763
#2 0x000000000098f82d in fcgi_finish_request (req=0x7fffa8c58ad0, force_close=0) at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fastcgi.c:1081
#3 0x0000000000999f55 in sapi_cgi_deactivate () at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fpm_main.c:847
#4 0x0000000000871a9b in sapi_deactivate () at /home/users/lihongbin/git/php-src/main/SAPI.c:536
#5 0x0000000000866580 in php_request_shutdown (dummy=0x0) at /home/users/lihongbin/git/php-src/main/main.c:1817
#6 0x000000000099c317 in main (argc=7, argv=0x7fffa8c5acb8) at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fpm_main.c:1981
````

solution
======

The nginx has the sample problem, but it own lingering mechanism to solve the problem
Lingering mechanism has three param:
- lingering_close : http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_close
- lingering_time : http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_time
- lingering_timeout : http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_timeout

Lingering mechanism just limit the time when server close the write socket and continued to receive the data from client in short

So I add two param in php.ini to limit the socket
- lingering_time_s : when server close the write socket , the maximum time to receive the data from client
- lingering_timeout_ms : a idle time control, when server close the write socket, if data are not received during this time, the connection is closed

I don't add the param of lingering_close , because we should read the last FCGI_STDIN header