diff --git a/NEWS b/NEWS index 300f44d70dd4..352ecbdae83c 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,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 000000000000..1d6b461a2ef4 --- /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-- + +--FILE-- + [ + '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-- + +--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 dcde68176b8b..f4f6a932efe7 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"); }