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
Lines changed: 117 additions & 0 deletions
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+
}
Lines changed: 99 additions & 0 deletions
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)
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