[php-src] master: Merge branch 'PHP-8.5'

Ilija Tovilo <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilija Tovilo (iluuu1994)
Date: 2026-07-01T12:04:19+02:00

Commit: https://github.com/php/php-src/commit/51dd32116e1dbbad6c0a19978ccfceef86ced66f
Raw diff: https://github.com/php/php-src/commit/51dd32116e1dbbad6c0a19978ccfceef86ced66f.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fixed timeout for supplemental read at end of a blocking stream in SSL stream wrapper

Changed paths:
  A  ext/openssl/tests/stream_supplemental_read_timeout.phpt
  M  ext/openssl/xp_ssl.c


Diff:

diff --git a/ext/openssl/tests/stream_supplemental_read_timeout.phpt b/ext/openssl/tests/stream_supplemental_read_timeout.phpt
new file mode 100644
index 000000000000..0e36f146bf9e
--- /dev/null
+++ b/ext/openssl/tests/stream_supplemental_read_timeout.phpt
@@ -0,0 +1,90 @@
+--TEST--
+Timeout for supplemental read at end of a blocking stream in SSL stream wrapper
+--EXTENSIONS--
+openssl
+--SKIPIF--
+<?php
+if (!function_exists("proc_open")) die("skip no proc_open");
+?>
+--FILE--
+<?php
+$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'crypto_supplemental_read_timeout.pem.tmp';
+$peerName = 'crypto-supplemental-read-timeout';
+
+$serverCode = <<<'CODE'
+    $ctx = stream_context_create(['ssl' => ['local_cert' => '%s']]);
+    $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+    $server = stream_socket_server("tls://127.0.0.1:0", $errno, $errstr, $flags, $ctx);
+    phpt_notify_server_start($server);
+
+    $conn = stream_socket_accept($server, 30);
+
+    fwrite($conn, "hello\n");
+
+    phpt_wait();
+    fclose($conn);
+CODE;
+$serverCode = sprintf($serverCode, $certFile);
+
+$clientCode = <<<'CODE'
+    $ctx = stream_context_create(['ssl' => [
+        'verify_peer' => false,
+        'verify_peer_name' => false,
+        'peer_name' => '%s',
+    ]]);
+
+    $client = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx);
+    stream_set_blocking($client, true);
+    stream_set_timeout($client, 5);
+    $start = hrtime(true);
+
+    $buf = '';
+    $read = [$client];
+    $write = $except = null;
+    while (true) {
+        if (!stream_select($read, $write, $except, 5)) {
+            break;
+        }
+
+        // Initially, read only the first char, then request more than is stored
+        // in the buffer, triggering a supplemental read.
+        $chunk = fread($client, strlen($buf) === 0 ? 1 : 10);
+        if ($chunk === '' || $chunk === false) {
+            /* A non-application record (e.g. a TLS 1.3 session ticket) may arrive first. */
+            if (feof($client)) {
+                break;
+            }
+        } else {
+            $buf .= $chunk;
+            if (strlen($buf) >= 6) {
+                break;
+            }
+        }
+        $read = [$client];
+        $write = $except = null;
+    }
+
+    echo trim($buf), "\n";
+
+    $diff = (hrtime(true) - $start) / 1e9;
+    var_dump($diff < 4.0);
+
+    phpt_notify();
+    fclose($client);
+CODE;
+$clientCode = sprintf($clientCode, $peerName);
+
+include 'CertificateGenerator.inc';
+$certificateGenerator = new CertificateGenerator();
+$certificateGenerator->saveNewCertAsFileWithKey($peerName, $certFile);
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'crypto_supplemental_read_timeout.pem.tmp');
+?>
+--EXPECT--
+hello
+bool(true)
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index bc483df617b1..92a17b60c5ce 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -2874,7 +2874,11 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si
 
 	/* Only do this if SSL is active. */
 	if (sslsock->ssl_active) {
-		int retry = 1;
+		/* We have already returned some buffered data. Don't retry and don't
+		 * block. We're just trying to fill the buffer more, but the stream might
+		 * be empty, so we don't want to wait in vain. */
+		bool supplemental = stream->has_buffered_data;
+		int retry = !supplemental;
 		struct timeval start_time;
 		struct timeval *timeout = NULL;
 		bool began_blocked = sslsock->s.is_blocked;
@@ -2887,11 +2891,11 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si
 		}
 
 		/* never use a timeout with non-blocking sockets */
-		if (began_blocked) {
+		if (began_blocked && !supplemental) {
 			timeout = &sslsock->s.timeout;
 		}
 
-		if (timeout) {
+		if (timeout || supplemental) {
 			php_openssl_set_blocking(sslsock, 0);
 		}
 
@@ -2966,7 +2970,7 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si
 				}
 
 				/* Don't loop indefinitely in non-blocking mode if no data is available */
-				if (began_blocked == 0) {
+				if (began_blocked == 0 || supplemental) {
 					break;
 				}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.