Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 52 additions & 28 deletions ext/openssl/tests/ServerClientTestCase.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -20,11 +22,11 @@ class ServerClientTestCase
{
private $isWorker = false;

private $workerHandle;
private $workerHandle = [];

private $workerStdIn;
private $workerStdIn = [];

private $workerStdOut;
private $workerStdOut = [];

private static $instance;

Expand All @@ -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)
Expand All @@ -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");
}
}

Expand Down
119 changes: 119 additions & 0 deletions ext/openssl/tests/bug77390.phpt
Original file line number Diff line number Diff line change
@@ -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"
34 changes: 13 additions & 21 deletions ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down