Skip to content

Commit af41d58

Browse files
committed
Merge branch 'PHP-8.3'
2 parents 894e17c + f4a9ae9 commit af41d58

File tree

2 files changed

+204
-13
lines changed

2 files changed

+204
-13
lines changed

ext/openssl/tests/gh10495.phpt

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
--TEST--
2+
GH-10495: feof hangs indefinitely
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("proc_open")) die("skip no proc_open");
8+
?>
9+
--FILE--
10+
<?php
11+
$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'gh10495.pem.tmp';
12+
$cacertFile = __DIR__ . DIRECTORY_SEPARATOR . 'gh10495-ca.pem.tmp';
13+
14+
$peerName = 'gh10495';
15+
$clientCode = <<<'CODE'
16+
$context = stream_context_create(['ssl' => ['verify_peer' => false, 'peer_name' => '%s']]);
17+
18+
phpt_wait('server');
19+
phpt_notify('proxy');
20+
21+
phpt_wait('proxy');
22+
$fp = stream_socket_client("tlsv1.2://127.0.0.1:10012", $errornum, $errorstr, 1, STREAM_CLIENT_CONNECT, $context);
23+
24+
phpt_wait('proxy');
25+
26+
$time = microtime(true);
27+
var_dump(feof($fp));
28+
var_dump(microtime(true) - $time < 0.5);
29+
30+
var_dump(stream_get_contents($fp, 6));
31+
32+
phpt_notify('server');
33+
phpt_notify('proxy');
34+
CODE;
35+
$clientCode = sprintf($clientCode, $peerName);
36+
37+
$serverCode = <<<'CODE'
38+
$context = stream_context_create(['ssl' => ['local_cert' => '%s']]);
39+
40+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
41+
$fp = stream_socket_server("tlsv1.2://127.0.0.1:10011", $errornum, $errorstr, $flags, $context);
42+
phpt_notify();
43+
44+
$conn = stream_socket_accept($fp);
45+
fwrite($conn, 'warmup');
46+
47+
phpt_wait();
48+
fclose($conn);
49+
CODE;
50+
$serverCode = sprintf($serverCode, $certFile);
51+
52+
$proxyCode = <<<'CODE'
53+
phpt_wait();
54+
55+
$upstream = stream_socket_client("tcp://127.0.0.1:10011", $errornum, $errorstr, 3000, STREAM_CLIENT_CONNECT);
56+
stream_set_blocking($upstream, false);
57+
58+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
59+
$server = stream_socket_server("tcp://127.0.0.1:10012", $errornum, $errorstr, $flags);
60+
phpt_notify();
61+
$conn = stream_socket_accept($server);
62+
stream_set_blocking($conn, false);
63+
64+
$read = [$upstream, $conn];
65+
$applicationData = false;
66+
$i = 1;
67+
while (stream_select($read, $write, $except, 1)) {
68+
foreach ($read as $fp) {
69+
$data = stream_get_contents($fp);
70+
if ($fp === $conn) {
71+
fwrite($upstream, $data);
72+
} else {
73+
if ($data !== '' && $data[0] === chr(23)) {
74+
if (!$applicationData) {
75+
$applicationData = true;
76+
fwrite($conn, $data[0]);
77+
phpt_notify();
78+
sleep(1);
79+
fwrite($conn, substr($data, 1));
80+
} else {
81+
fwrite($conn, $data);
82+
}
83+
} else {
84+
fwrite($conn, $data);
85+
}
86+
}
87+
}
88+
if (feof($upstream)) {
89+
break;
90+
}
91+
$read = [$upstream, $conn];
92+
}
93+
94+
phpt_wait();
95+
CODE;
96+
97+
include 'CertificateGenerator.inc';
98+
$certificateGenerator = new CertificateGenerator();
99+
$certificateGenerator->saveCaCert($cacertFile);
100+
$certificateGenerator->saveNewCertAsFileWithKey($peerName, $certFile);
101+
102+
include 'ServerClientTestCase.inc';
103+
ServerClientTestCase::getInstance()->run($clientCode, [
104+
'server' => $serverCode,
105+
'proxy' => $proxyCode,
106+
]);
107+
?>
108+
--CLEAN--
109+
<?php
110+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'gh10495.pem.tmp');
111+
@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'gh10495-ca.pem.tmp');
112+
?>
113+
--EXPECT--
114+
bool(false)
115+
bool(true)
116+
string(6) "warmup"

ext/openssl/xp_ssl.c

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,21 +2480,96 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val
24802480
/* the poll() call was skipped if the socket is non-blocking (or MSG_DONTWAIT is available) and if the timeout is zero */
24812481
/* additionally, we don't use this optimization if SSL is active because in that case, we're not using MSG_DONTWAIT */
24822482
if (sslsock->ssl_active) {
2483-
int n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf));
2484-
if (n <= 0) {
2485-
int err = SSL_get_error(sslsock->ssl_handle, n);
2486-
switch (err) {
2487-
case SSL_ERROR_SYSCALL:
2488-
alive = php_socket_errno() == EAGAIN;
2489-
break;
2490-
case SSL_ERROR_WANT_READ:
2491-
case SSL_ERROR_WANT_WRITE:
2492-
alive = 1;
2483+
int retry = 1;
2484+
struct timeval start_time;
2485+
struct timeval *timeout = NULL;
2486+
int began_blocked = sslsock->s.is_blocked;
2487+
int has_timeout = 0;
2488+
2489+
/* never use a timeout with non-blocking sockets */
2490+
if (began_blocked) {
2491+
timeout = &tv;
2492+
}
2493+
2494+
if (timeout && php_set_sock_blocking(sslsock->s.socket, 0) == SUCCESS) {
2495+
sslsock->s.is_blocked = 0;
2496+
}
2497+
2498+
if (!sslsock->s.is_blocked && timeout && (timeout->tv_sec > 0 || (timeout->tv_sec == 0 && timeout->tv_usec))) {
2499+
has_timeout = 1;
2500+
/* gettimeofday is not monotonic; using it here is not strictly correct */
2501+
gettimeofday(&start_time, NULL);
2502+
}
2503+
2504+
/* Main IO loop. */
2505+
do {
2506+
struct timeval cur_time, elapsed_time, left_time;
2507+
2508+
/* If we have a timeout to check, figure out how much time has elapsed since we started. */
2509+
if (has_timeout) {
2510+
gettimeofday(&cur_time, NULL);
2511+
2512+
/* Determine how much time we've taken so far. */
2513+
elapsed_time = php_openssl_subtract_timeval(cur_time, start_time);
2514+
2515+
/* and return an error if we've taken too long. */
2516+
if (php_openssl_compare_timeval(elapsed_time, *timeout) > 0 ) {
2517+
/* If the socket was originally blocking, set it back. */
2518+
if (began_blocked) {
2519+
php_set_sock_blocking(sslsock->s.socket, 1);
2520+
sslsock->s.is_blocked = 1;
2521+
}
2522+
sslsock->s.timeout_event = 1;
2523+
return PHP_STREAM_OPTION_RETURN_ERR;
2524+
}
2525+
}
2526+
2527+
int n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf));
2528+
/* If we didn't do anything on the last loop (or an error) check to see if we should retry or exit. */
2529+
if (n <= 0) {
2530+
/* Now, do the IO operation. Don't block if we can't complete... */
2531+
int err = SSL_get_error(sslsock->ssl_handle, n);
2532+
switch (err) {
2533+
case SSL_ERROR_SYSCALL:
2534+
retry = php_socket_errno() == EAGAIN;
2535+
break;
2536+
case SSL_ERROR_WANT_READ:
2537+
case SSL_ERROR_WANT_WRITE:
2538+
retry = 1;
2539+
break;
2540+
default:
2541+
/* any other problem is a fatal error */
2542+
retry = 0;
2543+
}
2544+
2545+
/* Don't loop indefinitely in non-blocking mode if no data is available */
2546+
if (began_blocked == 0 || !has_timeout) {
2547+
alive = retry;
24932548
break;
2494-
default:
2495-
/* any other problem is a fatal error */
2496-
alive = 0;
2549+
}
2550+
2551+
/* Now, if we have to wait some time, and we're supposed to be blocking, wait for the socket to become
2552+
* available. Now, php_pollfd_for uses select to wait up to our time_left value only...
2553+
*/
2554+
if (retry) {
2555+
/* Now, how much time until we time out? */
2556+
left_time = php_openssl_subtract_timeval(*timeout, elapsed_time);
2557+
if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI|POLLOUT, has_timeout ? &left_time : NULL) <= 0) {
2558+
retry = 0;
2559+
alive = 0;
2560+
};
2561+
}
2562+
} else {
2563+
retry = 0;
2564+
alive = 1;
24972565
}
2566+
/* Finally, we keep going until there are any data or there is no time to wait. */
2567+
} while (retry);
2568+
2569+
if (began_blocked && !sslsock->s.is_blocked) {
2570+
// Set it back to blocking
2571+
php_set_sock_blocking(sslsock->s.socket, 1);
2572+
sslsock->s.is_blocked = 1;
24982573
}
24992574
} else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK|MSG_DONTWAIT) && php_socket_errno() != EAGAIN) {
25002575
alive = 0;

0 commit comments

Comments
 (0)