Skip to content
Merged
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
115 changes: 115 additions & 0 deletions ext/openssl/tests/stream_server_reneg_limit_trampoline.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
--TEST--
reneg_limit_callback with a trampoline
--EXTENSIONS--
openssl
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
exec('openssl help', $out, $code);
if ($code > 0) die("skip couldn't locate openssl binary");
if(substr(PHP_OS, 0, 3) == 'WIN') {
die('skip not suitable for Windows');
}
?>
--FILE--
<?php
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'stream_server_reneg_limit.pem.tmp';

/**
* This test uses the openssl binary directly to initiate renegotiation. At this time it's not
* possible renegotiate the TLS handshake in PHP userland, so using the openssl s_client binary
* command is the only feasible way to test renegotiation limiting functionality. It's not an ideal
* solution, but it's really the only way to get test coverage on the rate-limiting functionality
* given current limitations.
*/

$serverCode = <<<'CODE'
class TrampolineTest {
private bool $printed = false;
public function __call(string $name, array $arguments) {
if (!$this->printed) {
$this->printed = true;
var_dump($arguments[0]);
}
}
}
$o = new TrampolineTest();
$callback = [$o, 'trampoline'];

$serverUri = "ssl://127.0.0.1:0";
$serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '%s',
// TLS 1.3 does not support renegotiation.
'max_proto_version' => STREAM_CRYPTO_PROTO_TLSv1_2,
'reneg_limit' => 0,
'reneg_window' => 30,
'reneg_limit_callback' => $callback,
]]);

$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
phpt_notify(message: stream_socket_get_name($server, false));

$clients = [];
while (1) {
$r = array_merge([$server], $clients);
$w = $e = [];

stream_select($r, $w, $e, $timeout=42);

foreach ($r as $sock) {
if ($sock === $server && ($client = stream_socket_accept($server, $timeout = 42))) {
$clientId = (int) $client;
$clients[$clientId] = $client;
} elseif ($sock !== $server) {
$clientId = (int) $sock;
$buffer = fread($sock, 1024);
if (strlen($buffer)) {
continue;
} elseif (!is_resource($sock) || feof($sock)) {
unset($clients[$clientId]);
break 2;
}
}
}
}
CODE;
$serverCode = sprintf($serverCode, $certFile);

$clientCode = <<<'CODE'
$serverUri = trim(phpt_wait());

$cmd = 'openssl s_client -connect ' . escapeshellarg($serverUri);
$descriptorSpec = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]];
$process = proc_open($cmd, $descriptorSpec, $pipes);

list($stdin, $stdout, $stderr) = $pipes;

// Trigger renegotiation twice
// Server settings only allow one per second (should result in disconnection)
fwrite($stdin, "R\nR\nR\nR\n");

$lines = [];
while(!feof($stderr)) {
fgets($stderr);
}

fclose($stdin);
fclose($stdout);
fclose($stderr);
proc_terminate($process);
CODE;

include 'CertificateGenerator.inc';
$certificateGenerator = new CertificateGenerator();
$certificateGenerator->saveNewCertAsFileWithKey('stream_security_level', $certFile);

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($serverCode, $clientCode, false);
?>
--CLEAN--
<?php
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'stream_server_reneg_limit.pem.tmp');
?>
--EXPECTF--
resource(%d) of type (stream)
14 changes: 9 additions & 5 deletions ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1203,23 +1203,27 @@ static void php_openssl_limit_handshake_reneg(const SSL *ssl) /* {{{ */
/* The token level exceeds our allowed limit */
if (sslsock->reneg->tokens > sslsock->reneg->limit) {
zval *val;


sslsock->reneg->should_close = 1;

if (PHP_STREAM_CONTEXT(stream) && (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream),
"ssl", "reneg_limit_callback")) != NULL
) {
zend_fcall_info_cache fcc;

if (!zend_is_callable(val, &fcc, NULL)) {
php_stream_warn(stream, UserspaceCallFailed, "SSL: failed invoking reneg limit notification callback");
return;
}
zval param, retval;

php_stream_to_zval(stream, &param);

/* Closing the stream inside this callback would segfault! */
stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
if (FAILURE == call_user_function(NULL, NULL, val, &retval, 1, &param)) {
php_stream_warn(stream, UserspaceCallFailed, "SSL: failed invoking reneg limit notification callback");
}
zend_call_known_fcc(&fcc, &retval, 1, &param, NULL);
stream->flags ^= PHP_STREAM_FLAG_NO_FCLOSE;
/* Release potential trampoline */
zend_release_fcall_info_cache(&fcc);

/* If the reneg_limit_callback returned true don't auto-close */
if (Z_TYPE(retval) == IS_TRUE) {
Expand Down
Loading