Commit 639a57d9e59 for php.net
commit 639a57d9e591cd7c1dff72e66bbe90ce96d5c18f
Author: Jakub Zelenka <bukka@php.net>
Date: Sat May 30 13:16:22 2026 +0200
Fix GH-22081: Memory leak in php_openssl_enable_crypto (#22189)
This was leaking because php_openssl_enable_crypto can be called
multiple times. The reneg was moved there after the session changes so
it needs to only happen once there.
The fix moves it (and some other parts that should be done just once)
inside state_set block where it can run only once.
diff --git a/ext/openssl/tests/gh22081.phpt b/ext/openssl/tests/gh22081.phpt
new file mode 100644
index 00000000000..17f74be7584
--- /dev/null
+++ b/ext/openssl/tests/gh22081.phpt
@@ -0,0 +1,76 @@
+--TEST--
+GH-22081: server reneg limit not reallocated across non-blocking retries
+--EXTENSIONS--
+openssl
+--SKIPIF--
+<?php
+if (!function_exists("proc_open")) die("skip no proc_open");
+?>
+--FILE--
+<?php
+$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'gh22081-server-reneg-nonblocking.pem.tmp';
+
+$serverCode = <<<'CODE'
+ $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+ $ctx = stream_context_create(['ssl' => [
+ 'local_cert' => '%s',
+ ]]);
+
+ /* Plain TCP listener so the TLS handshake is driven manually below. */
+ $server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
+ phpt_notify_server_start($server);
+
+ /* Complete each handshake in non-blocking mode so that php_openssl_enable_crypto() is
+ * re-entered across multiple WANT_READ/WANT_WRITE rounds per connection. */
+ for ($i = 0; $i < 5; $i++) {
+ $conn = @stream_socket_accept($server, 30);
+ if (!$conn) {
+ continue;
+ }
+ stream_set_blocking($conn, false);
+ do {
+ $r = stream_socket_enable_crypto($conn, true, STREAM_CRYPTO_METHOD_TLS_SERVER);
+ } while ($r === 0);
+
+ if ($r === true) {
+ fwrite($conn, "ok $i\n");
+ }
+ fclose($conn);
+ }
+CODE;
+$serverCode = sprintf($serverCode, $certFile);
+
+$clientCode = <<<'CODE'
+ $flags = STREAM_CLIENT_CONNECT;
+ $ctx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]]);
+
+ for ($i = 0; $i < 5; $i++) {
+ $client = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
+ if ($client) {
+ echo trim(fgets($client)) . "\n";
+ fclose($client);
+ }
+ }
+CODE;
+
+include 'CertificateGenerator.inc';
+$certificateGenerator = new CertificateGenerator();
+$certificateGenerator->saveNewCertAsFileWithKey('gh22081-server-reneg-nonblocking', $certFile);
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'gh22081-server-reneg-nonblocking.pem.tmp');
+?>
+--EXPECT--
+ok 0
+ok 1
+ok 2
+ok 3
+ok 4
+
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index 4d0dad20a43..307cc3489c3 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -2688,28 +2688,23 @@ static int php_openssl_enable_crypto(php_stream *stream,
struct timeval start_time, *timeout;
bool blocked = sslsock->s.is_blocked, has_timeout = false;
- if (sslsock->is_client) {
- /* Set session data for client */
- if ( php_openssl_apply_client_session_data(stream, sslsock)) {
- return FAILURE;
- }
-#ifdef HAVE_TLS_SNI
- php_openssl_enable_client_sni(stream, sslsock);
-#endif
- } else {
- php_openssl_init_server_reneg_limit(stream, sslsock);
- }
-
+ if (!sslsock->state_set) {
#ifdef PHP_OPENSSL_TLS_DEBUG
- BIO *b_out = BIO_new_fp(stdout, BIO_NOCLOSE | BIO_FP_TEXT);
- SSL_set_msg_callback(sslsock->ssl_handle, SSL_trace);
- SSL_set_msg_callback_arg(sslsock->ssl_handle, b_out);
+ BIO *b_out = BIO_new_fp(stdout, BIO_NOCLOSE | BIO_FP_TEXT);
+ SSL_set_msg_callback(sslsock->ssl_handle, SSL_trace);
+ SSL_set_msg_callback_arg(sslsock->ssl_handle, b_out);
#endif
-
- if (!sslsock->state_set) {
if (sslsock->is_client) {
+ /* Set session data for client */
+ if (php_openssl_apply_client_session_data(stream, sslsock) == FAILURE) {
+ return -1;
+ }
+#ifdef HAVE_TLS_SNI
+ php_openssl_enable_client_sni(stream, sslsock);
+#endif
SSL_set_connect_state(sslsock->ssl_handle);
} else {
+ php_openssl_init_server_reneg_limit(stream, sslsock);
SSL_set_accept_state(sslsock->ssl_handle);
}
sslsock->state_set = 1;