From: Abyr Valg Date: Sun, 29 Apr 2018 09:05:59 +0000 (+0300) Subject: Add a test for fragmented SSL packets X-Git-Tag: php-7.2.15RC1~5 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0c84c2ef503a531b43fa5f0f6b30e0d220c72e28;p=php Add a test for fragmented SSL packets --- diff --git a/ext/openssl/tests/ServerClientProxyTestCase.inc b/ext/openssl/tests/ServerClientProxyTestCase.inc new file mode 100644 index 0000000000..c55159f52a --- /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 0000000000..86b3815153 --- /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"