diff --git a/NEWS b/NEWS index 40393c15fb76..91ed81e4018a 100644 --- a/NEWS +++ b/NEWS @@ -120,6 +120,8 @@ PHP NEWS - OpenSSL: . Fixed stream_socket_enable_crypto() leaving the socket non-blocking after a handshake timeout. (Ilia Alshanetsky) + . Fixed feof() on a TLS stream staying false after a close_notify on Windows. + (Jakub Zelenka) - PCNTL: . Fixed pcntl_signal_dispatch() dropping the queued signals when it runs while diff --git a/ext/openssl/tests/stream_eof_after_close_notify.phpt b/ext/openssl/tests/stream_eof_after_close_notify.phpt new file mode 100644 index 000000000000..4755e171544d --- /dev/null +++ b/ext/openssl/tests/stream_eof_after_close_notify.phpt @@ -0,0 +1,58 @@ +--TEST-- +feof() is true after a TLS close_notify even if an earlier read left errno set to EAGAIN +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + ['local_cert' => '%s']]); + $sock = stream_socket_server("tls://127.0.0.1:0", $errno, $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $serverCtx); + phpt_notify_server_start($sock); + + $link = stream_socket_accept($sock); + /* Let the client block in fread() first, so its SSL_read() sees WANT_READ */ + phpt_wait(); + usleep(100000); + fwrite($link, "data"); + /* close_notify only, the TCP connection stays open */ + stream_socket_enable_crypto($link, false); + phpt_wait(); + fclose($link); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$clientCode = <<<'CODE' + $clientCtx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ]]); + $sock = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 2, STREAM_CLIENT_CONNECT, $clientCtx); + + phpt_notify(); + var_dump(fread($sock, 4)); + var_dump(fread($sock, 4)); + var_dump(feof($sock)); + phpt_notify(); +CODE; + +include 'CertificateGenerator.inc'; +(new CertificateGenerator())->saveNewCertAsFileWithKey('stream_eof_after_close_notify', $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--CLEAN-- + +--EXPECT-- +string(4) "data" +string(0) "" +bool(true) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index b564be8cad53..125b22423806 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2186,9 +2186,11 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si retry = 1; } - /* Also, on reads, we may get this condition on an EOF. We should check properly. */ if (read) { - stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); + /* EOF unless the SSL layer just needs to wait. */ + stream->eof = (retry == 0 + && err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE + && !SSL_pending(sslsock->ssl_handle)); } /* Don't loop indefinitely in non-blocking mode if no data is available */