[php-src] master: Merge branch 'PHP-8.4' into 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:09+02:00

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

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  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  NEWS
  M  ext/openssl/xp_ssl.c


Diff:

diff --git a/NEWS b/NEWS
index 020afbcc7197..a08132d60b54 100644
--- a/NEWS
+++ b/NEWS
@@ -19,6 +19,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-22443 (Tracing JIT SIGSEGV on megamorphic dynamic calls from
     an undereferenced run_time_cache map_ptr offset). (iliaal)
 
+- OpenSSL:
+  . Fixed timeout for supplemental read at end of a blocking stream in SSL
+    stream wrapper. (ilutov)
+
 - Intl:
   . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
     fallback locale when a language tag cannot be canonicalized. (Weilin Du)
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 c1b0332dca1c..cab8f9c859b4 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -1950,7 +1950,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;
@@ -1963,11 +1967,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);
 		}
 
@@ -2041,7 +2045,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.