Skip to content

Commit 0c84c2e

Browse files
valgabukka
authored andcommitted
Add a test for fragmented SSL packets
1 parent 08c5679 commit 0c84c2e

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
const WORKER_ARGV_VALUE = 'RUN_WORKER';
4+
5+
function phpt_notify($worker = null)
6+
{
7+
ServerClientProxyTestCase::getInstance()->notify($worker);
8+
}
9+
10+
function phpt_wait($worker = null)
11+
{
12+
ServerClientProxyTestCase::getInstance()->wait($worker);
13+
}
14+
15+
/**
16+
* This is a singleton to let the wait/notify functions work
17+
* I know it's horrible, but it's a means to an end
18+
*/
19+
class ServerClientProxyTestCase
20+
{
21+
private $isWorker = false;
22+
23+
private $workerHandles = [];
24+
25+
private $workerStdIn = [];
26+
27+
private $workerStdOut = [];
28+
29+
private static $instance;
30+
31+
public static function getInstance($isWorker = false)
32+
{
33+
if (!isset(self::$instance)) {
34+
self::$instance = new self($isWorker);
35+
}
36+
37+
return self::$instance;
38+
}
39+
40+
public function __construct($isWorker = false)
41+
{
42+
if (!isset(self::$instance)) {
43+
self::$instance = $this;
44+
}
45+
46+
$this->isWorker = $isWorker;
47+
}
48+
49+
private function spawnWorkerProcess($worker, $code)
50+
{
51+
if (defined("PHP_WINDOWS_VERSION_MAJOR")) {
52+
$ini = php_ini_loaded_file();
53+
$cmd = sprintf('%s %s "%s" %s', PHP_BINARY, $ini ? "-n -c $ini" : "", __FILE__, WORKER_ARGV_VALUE);
54+
} else {
55+
$cmd = sprintf('%s "%s" %s %s', PHP_BINARY, __FILE__, WORKER_ARGV_VALUE, $worker);
56+
}
57+
$this->workerHandle[$worker] = proc_open($cmd, [['pipe', 'r'], ['pipe', 'w'], STDERR], $pipes);
58+
$this->workerStdIn[$worker] = $pipes[0];
59+
$this->workerStdOut[$worker] = $pipes[1];
60+
61+
fwrite($this->workerStdIn[$worker], $code . "\n---\n");
62+
}
63+
64+
private function cleanupWorkerProcess($worker)
65+
{
66+
fclose($this->workerStdIn[$worker]);
67+
fclose($this->workerStdOut[$worker]);
68+
proc_close($this->workerHandle[$worker]);
69+
}
70+
71+
private function stripPhpTagsFromCode($code)
72+
{
73+
return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code);
74+
}
75+
76+
public function runWorker()
77+
{
78+
$code = '';
79+
80+
while (1) {
81+
$line = fgets(STDIN);
82+
83+
if (trim($line) === "---") {
84+
break;
85+
}
86+
87+
$code .= $line;
88+
}
89+
90+
eval($code);
91+
}
92+
93+
public function run($testCode, array $workerCodes)
94+
{
95+
foreach ($workerCodes as $worker => $code) {
96+
$this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
97+
}
98+
eval($this->stripPhpTagsFromCode($testCode));
99+
foreach ($workerCodes as $worker => $code) {
100+
$this->cleanupWorkerProcess($worker);
101+
}
102+
}
103+
104+
public function wait($worker)
105+
{
106+
fgets($this->isWorker ? STDIN : $this->workerStdOut[$worker]);
107+
}
108+
109+
public function notify($worker)
110+
{
111+
fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
112+
}
113+
}
114+
115+
if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) {
116+
ServerClientProxyTestCase::getInstance(true)->runWorker();
117+
}
+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
--TEST--
2+
php_stream_eof() should not block on SSL non-blocking streams when packets are fragmented
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("openssl")) die("skip openssl not loaded");
6+
if (!function_exists("proc_open")) die("skip no proc_open");
7+
?>
8+
--FILE--
9+
<?php
10+
11+
$clientCode = <<<'CODE'
12+
$context = stream_context_create(['ssl' => ['verify_peer' => false, 'peer_name' => 'bug54992.local']]);
13+
14+
phpt_wait('server');
15+
phpt_notify('proxy');
16+
17+
phpt_wait('proxy');
18+
$fp = stream_socket_client("ssl://127.0.0.1:10012", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT, $context);
19+
stream_set_blocking($fp, false);
20+
21+
$read = [$fp];
22+
$buf = '';
23+
while (stream_select($read, $write, $except, 1000)) {
24+
$chunk = stream_get_contents($fp, 4096);
25+
var_dump($chunk);
26+
$buf .= $chunk;
27+
if ($buf === 'hello, world') {
28+
break;
29+
}
30+
}
31+
32+
phpt_notify('server');
33+
phpt_notify('proxy');
34+
CODE;
35+
36+
$serverCode = <<<'CODE'
37+
$context = stream_context_create(['ssl' => ['local_cert' => __DIR__ . '/bug54992.pem']]);
38+
39+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
40+
$fp = stream_socket_server("ssl://127.0.0.1:10011", $errornum, $errorstr, $flags, $context);
41+
phpt_notify();
42+
43+
$conn = stream_socket_accept($fp);
44+
fwrite($conn, 'hello, world');
45+
46+
phpt_wait();
47+
fclose($conn);
48+
CODE;
49+
50+
$proxyCode = <<<'CODE'
51+
phpt_wait();
52+
53+
$upstream = stream_socket_client("tcp://127.0.0.1:10011", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT);
54+
stream_set_blocking($upstream, false);
55+
56+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
57+
$server = stream_socket_server("tcp://127.0.0.1:10012", $errornum, $errorstr, $flags);
58+
phpt_notify();
59+
60+
$conn = stream_socket_accept($server);
61+
stream_set_blocking($conn, false);
62+
63+
$read = [$upstream, $conn];
64+
while (stream_select($read, $write, $except, 1)) {
65+
foreach ($read as $fp) {
66+
$data = stream_get_contents($fp);
67+
if ($fp === $conn) {
68+
fwrite($upstream, $data);
69+
} else {
70+
if ($data !== '' && $data[0] === chr(23)) {
71+
$parts = str_split($data, (int) ceil(strlen($data) / 3));
72+
foreach ($parts as $part) {
73+
fwrite($conn, $part);
74+
usleep(1000);
75+
}
76+
} else {
77+
fwrite($conn, $data);
78+
}
79+
}
80+
}
81+
if (feof($upstream)) {
82+
break;
83+
}
84+
$read = [$upstream, $conn];
85+
}
86+
87+
phpt_wait();
88+
CODE;
89+
90+
include 'ServerClientProxyTestCase.inc';
91+
ServerClientProxyTestCase::getInstance()->run($clientCode, [
92+
'server' => $serverCode,
93+
'proxy' => $proxyCode,
94+
]);
95+
?>
96+
--EXPECT--
97+
string(0) ""
98+
string(0) ""
99+
string(12) "hello, world"

0 commit comments

Comments
 (0)