diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc index f0e40fa53545..4bad3c2995ae 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 000000000000..7f153296401f --- /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-- + +--FILE-- + ['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-- + +--EXPECT-- +string(0) "" +string(12) "hello, world" diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index a5a4c4dbfb2c..5117eb2e99f4 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -2405,30 +2405,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; }
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: