summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--ext/openssl/tests/ServerClientTestCase.inc80
-rw-r--r--ext/openssl/tests/bug77390.phpt119
-rw-r--r--ext/openssl/xp_ssl.c34
4 files changed, 188 insertions, 49 deletions
diff --git a/NEWS b/NEWS
index 2a77346cc2..4fbed9e28f 100644
--- a/NEWS
+++ b/NEWS
@@ -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;
}