Commit 24ca066b464 for php

commit 24ca066b4649eea0b9a8abfa574fad67b834cca2
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Thu Nov 5 09:49:11 2026 -0500

    ext/openssl: Propagate session_get_cb exceptions unchanged

    An exception thrown by session_get_cb left the call's return value
    undefined, which the return type check then reported as a TypeError
    chained over the user's exception. Skip that check for an undefined
    return value, as the PSK callbacks already do, so the original exception
    reaches the caller.

    Closes GH-25218

diff --git a/NEWS b/NEWS
index af1e692a180..a25df1bb1ca 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,8 @@ PHP                                                                        NEWS
 - OpenSSL:
   . Fixed bug GH-25172 (TLS session reuse shares request-lifetime callback
     structures with persistent streams). (cxxz16, David Carlier)
+  . Fixed an exception thrown by the session_get_cb stream context callback
+    being replaced with a TypeError. (Ilia Alshanetsky)

 05 Nov 2026, PHP 8.6.0RC5

diff --git a/ext/openssl/tests/session_resumption_get_cb_throws.phpt b/ext/openssl/tests/session_resumption_get_cb_throws.phpt
new file mode 100644
index 00000000000..1d6b461a2ef
--- /dev/null
+++ b/ext/openssl/tests/session_resumption_get_cb_throws.phpt
@@ -0,0 +1,98 @@
+--TEST--
+TLS session resumption - exception thrown by session_get_cb propagates unchanged
+--EXTENSIONS--
+openssl
+--SKIPIF--
+<?php
+if (!function_exists("proc_open")) die("skip no proc_open");
+?>
+--FILE--
+<?php
+$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'session_get_cb_throws.pem.tmp';
+
+$serverCode = <<<'CODE'
+    $sessionStore = [];
+
+    $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+    $ctx = stream_context_create(['ssl' => [
+        'local_cert' => '%s',
+        'session_id_context' => 'test-server',
+        'session_new_cb' => function ($stream, $session) use (&$sessionStore) {
+            $sessionStore[bin2hex($session->id)] = $session;
+        },
+        'session_get_cb' => function ($stream, $sessionId) use (&$sessionStore) {
+            if (isset($sessionStore[bin2hex($sessionId)])) {
+                throw new RuntimeException('session_get_cb failed');
+            }
+            return null;
+        },
+    ], 'stream' => ['error_mode' => StreamErrorMode::%s]]);
+
+    $server = stream_socket_server('tlsv1.2://127.0.0.1:0', $errno, $errstr, $flags, $ctx);
+    phpt_notify_server_start($server);
+
+    $client = @stream_socket_accept($server, 30);
+    fwrite($client, "Response 1\n");
+    fclose($client);
+
+    try {
+        $client = @stream_socket_accept($server, 30);
+        $result = "accepted " . get_debug_type($client);
+    } catch (Throwable $e) {
+        $result = $e::class . ": " . $e->getMessage() . ", previous: " . get_debug_type($e->getPrevious());
+    }
+
+    phpt_notify(message: $result);
+CODE;
+
+$clientCode = <<<'CODE'
+    $sessionData = null;
+
+    $flags = STREAM_CLIENT_CONNECT;
+    $ctx = stream_context_create(['ssl' => [
+        'verify_peer' => false,
+        'verify_peer_name' => false,
+        'session_new_cb' => function ($stream, $session) use (&$sessionData) {
+            $sessionData = $session;
+        },
+    ]]);
+
+    $client1 = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx);
+    echo trim(fgets($client1)), "\n";
+    fclose($client1);
+
+    $ctx2 = stream_context_create(['ssl' => [
+        'verify_peer' => false,
+        'verify_peer_name' => false,
+        'session_data' => $sessionData,
+    ]]);
+
+    $client2 = @stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 30, $flags, $ctx2);
+    var_dump($client2);
+
+    echo "Server: ", trim(phpt_wait()), "\n";
+CODE;
+
+include 'CertificateGenerator.inc';
+$certificateGenerator = new CertificateGenerator();
+$certificateGenerator->saveNewCertAsFileWithKey('session_get_cb_throws', $certFile);
+
+include 'ServerClientTestCase.inc';
+foreach (['Error', 'Exception'] as $mode) {
+    echo "$mode mode:\n";
+    ServerClientTestCase::getInstance()->run($clientCode, sprintf($serverCode, $certFile, $mode));
+}
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'session_get_cb_throws.pem.tmp');
+?>
+--EXPECTF--
+Error mode:
+Response 1
+resource(%d) of type (stream)
+Server: RuntimeException: session_get_cb failed, previous: null
+Exception mode:
+Response 1
+resource(%d) of type (stream)
+Server: RuntimeException: session_get_cb failed, previous: null
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index dcde68176b8..f4f6a932efe 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -2266,7 +2266,7 @@ static SSL_SESSION *php_openssl_session_get_cb(SSL *ssl, const unsigned char *se
 			SSL_SESSION_up_ref(obj->session);
 			session = obj->session;
 		}
-	} else if (Z_TYPE(retval) != IS_NULL) {
+	} else if (!Z_ISUNDEF(retval) && Z_TYPE(retval) != IS_NULL) {
 		zend_type_error("session_get_cb return type must be null or Openssl\\Session");
 	}