From 2d2de10c6540cc7e974fd3ba64ed7ecb9bab0752 Mon Sep 17 00:00:00 2001 From: Abyr Valg Date: Sun, 29 Apr 2018 12:05:59 +0300 Subject: [PATCH 1/3] Add a test for fragmented SSL packets --- .../tests/ServerClientProxyTestCase.inc | 117 ++++++++++++++++++ ext/openssl/tests/non_blocking_eof.phpt | 99 +++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 ext/openssl/tests/ServerClientProxyTestCase.inc create mode 100644 ext/openssl/tests/non_blocking_eof.phpt diff --git a/ext/openssl/tests/ServerClientProxyTestCase.inc b/ext/openssl/tests/ServerClientProxyTestCase.inc new file mode 100644 index 000000000000..c55159f52aa5 --- /dev/null +++ b/ext/openssl/tests/ServerClientProxyTestCase.inc @@ -0,0 +1,117 @@ +notify($worker); +} + +function phpt_wait($worker = null) +{ + ServerClientProxyTestCase::getInstance()->wait($worker); +} + +/** + * This is a singleton to let the wait/notify functions work + * I know it's horrible, but it's a means to an end + */ +class ServerClientProxyTestCase +{ + private $isWorker = false; + + private $workerHandles = []; + + private $workerStdIn = []; + + private $workerStdOut = []; + + private static $instance; + + public static function getInstance($isWorker = false) + { + if (!isset(self::$instance)) { + self::$instance = new self($isWorker); + } + + return self::$instance; + } + + public function __construct($isWorker = false) + { + if (!isset(self::$instance)) { + self::$instance = $this; + } + + $this->isWorker = $isWorker; + } + + 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); + } else { + $cmd = sprintf('%s "%s" %s %s', PHP_BINARY, __FILE__, WORKER_ARGV_VALUE, $worker); + } + $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($worker) + { + fclose($this->workerStdIn[$worker]); + fclose($this->workerStdOut[$worker]); + proc_close($this->workerHandle[$worker]); + } + + private function stripPhpTagsFromCode($code) + { + return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code); + } + + public function runWorker() + { + $code = ''; + + while (1) { + $line = fgets(STDIN); + + if (trim($line) === "---") { + break; + } + + $code .= $line; + } + + eval($code); + } + + public function run($testCode, array $workerCodes) + { + foreach ($workerCodes as $worker => $code) { + $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); + } + eval($this->stripPhpTagsFromCode($testCode)); + foreach ($workerCodes as $worker => $code) { + $this->cleanupWorkerProcess($worker); + } + } + + public function wait($worker) + { + fgets($this->isWorker ? STDIN : $this->workerStdOut[$worker]); + } + + public function notify($worker) + { + fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n"); + } +} + +if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) { + ServerClientProxyTestCase::getInstance(true)->runWorker(); +} diff --git a/ext/openssl/tests/non_blocking_eof.phpt b/ext/openssl/tests/non_blocking_eof.phpt new file mode 100644 index 000000000000..86b3815153e1 --- /dev/null +++ b/ext/openssl/tests/non_blocking_eof.phpt @@ -0,0 +1,99 @@ +--TEST-- +php_stream_eof() should not block on SSL non-blocking streams when packets are fragmented +--SKIPIF-- + +--FILE-- + ['verify_peer' => false, 'peer_name' => 'bug54992.local']]); + + 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 = ''; + while (stream_select($read, $write, $except, 1000)) { + $chunk = stream_get_contents($fp, 4096); + var_dump($chunk); + $buf .= $chunk; + if ($buf === 'hello, world') { + break; + } + } + + phpt_notify('server'); + phpt_notify('proxy'); +CODE; + +$serverCode = <<<'CODE' + $context = stream_context_create(['ssl' => ['local_cert' => __DIR__ . '/bug54992.pem']]); + + $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; + +$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 'ServerClientProxyTestCase.inc'; +ServerClientProxyTestCase::getInstance()->run($clientCode, [ + 'server' => $serverCode, + 'proxy' => $proxyCode, +]); +?> +--EXPECT-- +string(0) "" +string(0) "" +string(12) "hello, world" From 4cccfb25357449bb1dcd85caec5fe9f0f62332d8 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 16 Jan 2019 18:14:30 +0000 Subject: [PATCH 2/3] Update and integrate openssl client proxy test --- .../tests/ServerClientProxyTestCase.inc | 117 ------------------ ext/openssl/tests/ServerClientTestCase.inc | 80 +++++++----- .../{non_blocking_eof.phpt => bug77390.phpt} | 25 +++- 3 files changed, 72 insertions(+), 150 deletions(-) delete mode 100644 ext/openssl/tests/ServerClientProxyTestCase.inc rename ext/openssl/tests/{non_blocking_eof.phpt => bug77390.phpt} (71%) diff --git a/ext/openssl/tests/ServerClientProxyTestCase.inc b/ext/openssl/tests/ServerClientProxyTestCase.inc deleted file mode 100644 index c55159f52aa5..000000000000 --- a/ext/openssl/tests/ServerClientProxyTestCase.inc +++ /dev/null @@ -1,117 +0,0 @@ -notify($worker); -} - -function phpt_wait($worker = null) -{ - ServerClientProxyTestCase::getInstance()->wait($worker); -} - -/** - * This is a singleton to let the wait/notify functions work - * I know it's horrible, but it's a means to an end - */ -class ServerClientProxyTestCase -{ - private $isWorker = false; - - private $workerHandles = []; - - private $workerStdIn = []; - - private $workerStdOut = []; - - private static $instance; - - public static function getInstance($isWorker = false) - { - if (!isset(self::$instance)) { - self::$instance = new self($isWorker); - } - - return self::$instance; - } - - public function __construct($isWorker = false) - { - if (!isset(self::$instance)) { - self::$instance = $this; - } - - $this->isWorker = $isWorker; - } - - 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); - } else { - $cmd = sprintf('%s "%s" %s %s', PHP_BINARY, __FILE__, WORKER_ARGV_VALUE, $worker); - } - $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($worker) - { - fclose($this->workerStdIn[$worker]); - fclose($this->workerStdOut[$worker]); - proc_close($this->workerHandle[$worker]); - } - - private function stripPhpTagsFromCode($code) - { - return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code); - } - - public function runWorker() - { - $code = ''; - - while (1) { - $line = fgets(STDIN); - - if (trim($line) === "---") { - break; - } - - $code .= $line; - } - - eval($code); - } - - public function run($testCode, array $workerCodes) - { - foreach ($workerCodes as $worker => $code) { - $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); - } - eval($this->stripPhpTagsFromCode($testCode)); - foreach ($workerCodes as $worker => $code) { - $this->cleanupWorkerProcess($worker); - } - } - - public function wait($worker) - { - fgets($this->isWorker ? STDIN : $this->workerStdOut[$worker]); - } - - public function notify($worker) - { - fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n"); - } -} - -if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) { - ServerClientProxyTestCase::getInstance(true)->runWorker(); -} 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/non_blocking_eof.phpt b/ext/openssl/tests/bug77390.phpt similarity index 71% rename from ext/openssl/tests/non_blocking_eof.phpt rename to ext/openssl/tests/bug77390.phpt index 86b3815153e1..d746a5d4aac1 100644 --- a/ext/openssl/tests/non_blocking_eof.phpt +++ b/ext/openssl/tests/bug77390.phpt @@ -1,5 +1,5 @@ --TEST-- -php_stream_eof() should not block on SSL non-blocking streams when packets are fragmented +Bug #76705: feof might hang on TLS streams in case of fragmented TLS records --SKIPIF-- --FILE-- ['verify_peer' => false, 'peer_name' => 'bug54992.local']]); + $context = stream_context_create(['ssl' => ['verify_peer' => false, 'peer_name' => '%s']]); phpt_wait('server'); phpt_notify('proxy'); @@ -32,9 +35,10 @@ $clientCode = <<<'CODE' phpt_notify('server'); phpt_notify('proxy'); CODE; +$clientCode = sprintf($clientCode, $peerName); $serverCode = <<<'CODE' - $context = stream_context_create(['ssl' => ['local_cert' => __DIR__ . '/bug54992.pem']]); + $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); @@ -46,6 +50,7 @@ $serverCode = <<<'CODE' phpt_wait(); fclose($conn); CODE; +$serverCode = sprintf($serverCode, $certFile); $proxyCode = <<<'CODE' phpt_wait(); @@ -87,12 +92,22 @@ $proxyCode = <<<'CODE' phpt_wait(); CODE; -include 'ServerClientProxyTestCase.inc'; -ServerClientProxyTestCase::getInstance()->run($clientCode, [ +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(0) "" From 276338a730215750e5570a361f030af3e7acfaa0 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 17 Jan 2019 18:05:13 +0000 Subject: [PATCH 3/3] Fix bug #77390 (feof might hang on TLS streams in case of fragmented TLS records) Simplified version of the fix from Abyl Valg so credit to him. --- ext/openssl/tests/bug77390.phpt | 11 ++++++++--- ext/openssl/xp_ssl.c | 34 +++++++++++++-------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/ext/openssl/tests/bug77390.phpt b/ext/openssl/tests/bug77390.phpt index d746a5d4aac1..7f153296401f 100644 --- a/ext/openssl/tests/bug77390.phpt +++ b/ext/openssl/tests/bug77390.phpt @@ -23,10 +23,16 @@ $clientCode = <<<'CODE' $read = [$fp]; $buf = ''; + $printed = false; while (stream_select($read, $write, $except, 1000)) { $chunk = stream_get_contents($fp, 4096); - var_dump($chunk); - $buf .= $chunk; + if ($chunk !== "") { + var_dump($chunk); + $buf .= $chunk; + } elseif (!$printed) { + $printed = true; + var_dump($chunk); + } if ($buf === 'hello, world') { break; } @@ -110,5 +116,4 @@ ServerClientTestCase::getInstance()->run($clientCode, [ ?> --EXPECT-- string(0) "" -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; } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy