diff options
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | ext/openssl/tests/ServerClientTestCase.inc | 80 | ||||
-rw-r--r-- | ext/openssl/tests/bug77390.phpt | 119 | ||||
-rw-r--r-- | ext/openssl/xp_ssl.c | 34 |
4 files changed, 188 insertions, 49 deletions
@@ -16,6 +16,10 @@ PHP NEWS . Fixed bug #77287 (Opcache literal compaction is incompatible with EXT opcodes). (Nikita) +- OpenSSL: + . Fixed bug #77390 (feof might hang on TLS streams in case of fragmented TLS + records). (Abyl Valg, Jakub Zelenka) + 07 Feb 2019, PHP 7.3.2 - Core: diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc index f0e40fa535..4bad3c2995 100644 --- a/ext/openssl/tests/ServerClientTestCase.inc +++ b/ext/openssl/tests/ServerClientTestCase.inc @@ -2,14 +2,16 @@ const WORKER_ARGV_VALUE = 'RUN_WORKER'; -function phpt_notify() +const WORKER_DEFAULT_NAME = 'server'; + +function phpt_notify($worker = WORKER_DEFAULT_NAME) { - ServerClientTestCase::getInstance()->notify(); + ServerClientTestCase::getInstance()->notify($worker); } -function phpt_wait() +function phpt_wait($worker = WORKER_DEFAULT_NAME) { - ServerClientTestCase::getInstance()->wait(); + ServerClientTestCase::getInstance()->wait($worker); } /** @@ -20,11 +22,11 @@ class ServerClientTestCase { private $isWorker = false; - private $workerHandle; + private $workerHandle = []; - private $workerStdIn; + private $workerStdIn = []; - private $workerStdOut; + private $workerStdOut = []; private static $instance; @@ -46,26 +48,41 @@ class ServerClientTestCase $this->isWorker = $isWorker; } - private function spawnWorkerProcess($code) + private function spawnWorkerProcess($worker, $code) { if (defined("PHP_WINDOWS_VERSION_MAJOR")) { - $ini = php_ini_loaded_file(); - $cmd = sprintf('%s %s "%s" %s', PHP_BINARY, $ini ? "-n -c $ini" : "", __FILE__, WORKER_ARGV_VALUE); + $ini = php_ini_loaded_file(); + $cmd = sprintf( + '%s %s "%s" %s', + PHP_BINARY, $ini ? "-n -c $ini" : "", + __FILE__, + WORKER_ARGV_VALUE + ); } else { - $cmd = sprintf('%s "%s" %s', PHP_BINARY, __FILE__, WORKER_ARGV_VALUE); + $cmd = sprintf( + '%s "%s" %s %s', + PHP_BINARY, + __FILE__, + WORKER_ARGV_VALUE, + $worker + ); } - $this->workerHandle = proc_open($cmd, [['pipe', 'r'], ['pipe', 'w'], STDERR], $pipes); - $this->workerStdIn = $pipes[0]; - $this->workerStdOut = $pipes[1]; - - fwrite($this->workerStdIn, $code . "\n---\n"); + $this->workerHandle[$worker] = proc_open( + $cmd, + [['pipe', 'r'], ['pipe', 'w'], STDERR], + $pipes + ); + $this->workerStdIn[$worker] = $pipes[0]; + $this->workerStdOut[$worker] = $pipes[1]; + + fwrite($this->workerStdIn[$worker], $code . "\n---\n"); } - private function cleanupWorkerProcess() + private function cleanupWorkerProcess($worker) { - fclose($this->workerStdIn); - fclose($this->workerStdOut); - proc_close($this->workerHandle); + fclose($this->workerStdIn[$worker]); + fclose($this->workerStdOut[$worker]); + proc_close($this->workerHandle[$worker]); } private function stripPhpTagsFromCode($code) @@ -90,21 +107,28 @@ class ServerClientTestCase eval($code); } - public function run($proc1Code, $proc2Code) + public function run($masterCode, $workerCode) { - $this->spawnWorkerProcess($this->stripPhpTagsFromCode($proc2Code)); - eval($this->stripPhpTagsFromCode($proc1Code)); - $this->cleanupWorkerProcess(); + if (!is_array($workerCode)) { + $workerCode = [WORKER_DEFAULT_NAME => $workerCode]; + } + foreach ($workerCode as $worker => $code) { + $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); + } + eval($this->stripPhpTagsFromCode($masterCode)); + foreach ($workerCode as $worker => $code) { + $this->cleanupWorkerProcess($worker); + } } - public function wait() + public function wait($worker) { - fgets($this->isWorker ? STDIN : $this->workerStdOut); + fgets($this->isWorker ? STDIN : $this->workerStdOut[$worker]); } - public function notify() + public function notify($worker) { - fwrite($this->isWorker ? STDOUT : $this->workerStdIn, "\n"); + fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n"); } } diff --git a/ext/openssl/tests/bug77390.phpt b/ext/openssl/tests/bug77390.phpt new file mode 100644 index 0000000000..7f15329640 --- /dev/null +++ b/ext/openssl/tests/bug77390.phpt @@ -0,0 +1,119 @@ +--TEST-- +Bug #76705: feof might hang on TLS streams in case of fragmented TLS records +--SKIPIF-- +<?php +if (!extension_loaded("openssl")) die("skip openssl not loaded"); +if (!function_exists("proc_open")) die("skip no proc_open"); +?> +--FILE-- +<?php +$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'bug77390.pem.tmp'; +$cacertFile = __DIR__ . DIRECTORY_SEPARATOR . 'bug77390-ca.pem.tmp'; + +$peerName = 'bug77390'; +$clientCode = <<<'CODE' + $context = stream_context_create(['ssl' => ['verify_peer' => false, 'peer_name' => '%s']]); + + phpt_wait('server'); + phpt_notify('proxy'); + + phpt_wait('proxy'); + $fp = stream_socket_client("ssl://127.0.0.1:10012", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT, $context); + stream_set_blocking($fp, false); + + $read = [$fp]; + $buf = ''; + $printed = false; + while (stream_select($read, $write, $except, 1000)) { + $chunk = stream_get_contents($fp, 4096); + if ($chunk !== "") { + var_dump($chunk); + $buf .= $chunk; + } elseif (!$printed) { + $printed = true; + var_dump($chunk); + } + if ($buf === 'hello, world') { + break; + } + } + + phpt_notify('server'); + phpt_notify('proxy'); +CODE; +$clientCode = sprintf($clientCode, $peerName); + +$serverCode = <<<'CODE' + $context = stream_context_create(['ssl' => ['local_cert' => '%s']]); + + $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; + $fp = stream_socket_server("ssl://127.0.0.1:10011", $errornum, $errorstr, $flags, $context); + phpt_notify(); + + $conn = stream_socket_accept($fp); + fwrite($conn, 'hello, world'); + + phpt_wait(); + fclose($conn); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$proxyCode = <<<'CODE' + phpt_wait(); + + $upstream = stream_socket_client("tcp://127.0.0.1:10011", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT); + stream_set_blocking($upstream, false); + + $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; + $server = stream_socket_server("tcp://127.0.0.1:10012", $errornum, $errorstr, $flags); + phpt_notify(); + + $conn = stream_socket_accept($server); + stream_set_blocking($conn, false); + + $read = [$upstream, $conn]; + while (stream_select($read, $write, $except, 1)) { + foreach ($read as $fp) { + $data = stream_get_contents($fp); + if ($fp === $conn) { + fwrite($upstream, $data); + } else { + if ($data !== '' && $data[0] === chr(23)) { + $parts = str_split($data, (int) ceil(strlen($data) / 3)); + foreach ($parts as $part) { + fwrite($conn, $part); + usleep(1000); + } + } else { + fwrite($conn, $data); + } + } + } + if (feof($upstream)) { + break; + } + $read = [$upstream, $conn]; + } + + phpt_wait(); +CODE; + +include 'CertificateGenerator.inc'; +$certificateGenerator = new CertificateGenerator(); +$certificateGenerator->saveCaCert($cacertFile); +$certificateGenerator->saveNewCertAsFileWithKey($peerName, $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, [ + 'server' => $serverCode, + 'proxy' => $proxyCode, +]); +?> +--CLEAN-- +<?php +@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'bug77390.pem.tmp'); +@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'bug77390-ca.pem.tmp'); +?> +--EXPECT-- +string(0) "" +string(12) "hello, world" diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 11bea41295..8697d2d3b2 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2453,30 +2453,22 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val alive = 0; } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { if (sslsock->ssl_active) { - int n; - - do { - n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); - if (n <= 0) { - int err = SSL_get_error(sslsock->ssl_handle, n); - - if (err == SSL_ERROR_SYSCALL) { + int n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); + if (n <= 0) { + int err = SSL_get_error(sslsock->ssl_handle, n); + switch (err) { + case SSL_ERROR_SYSCALL: alive = php_socket_errno() == EAGAIN; break; - } - - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { - /* re-negotiate */ - continue; - } - - /* any other problem is a fatal error */ - alive = 0; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + alive = 1; + break; + default: + /* any other problem is a fatal error */ + alive = 0; } - /* either peek succeeded or there was an error; we - * have set the alive flag appropriately */ - break; - } while (1); + } } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { alive = 0; } |