diff --git a/NEWS b/NEWS index 300f44d70dd4..0fd53de990d7 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ PHP NEWS - OpenSSL: . Fixed bug GH-25172 (TLS session reuse shares request-lifetime callback structures with persistent streams). (cxxz16, David Carlier) + . Fixed use-after-free in TLS server ALPN and Windows certificate checks + when the session stream passed to stream_socket_enable_crypto() is closed + before the handshake. (Ilia Alshanetsky) 05 Nov 2026, PHP 8.6.0RC5 diff --git a/ext/openssl/tests/session_resumption_server_alpn_closed_parent.phpt b/ext/openssl/tests/session_resumption_server_alpn_closed_parent.phpt new file mode 100644 index 000000000000..f612fd5fbf98 --- /dev/null +++ b/ext/openssl/tests/session_resumption_server_alpn_closed_parent.phpt @@ -0,0 +1,59 @@ +--TEST-- +Server ALPN selection on a stream seeded from a closed session stream +--EXTENSIONS-- +openssl +--FILE-- +saveNewCertAsFileWithKey('localhost', $certFile); + +$server = stream_socket_server('tcp://127.0.0.1:0'); +$addr = stream_socket_get_name($server, false); + +$parentClient = stream_socket_client("tcp://$addr"); +$parent = stream_socket_accept($server); +stream_context_set_options($parent, ['ssl' => [ + 'local_cert' => $certFile, + 'alpn_protocols' => 'h2,http/1.1', +]]); +stream_set_blocking($parent, false); +var_dump(stream_socket_enable_crypto($parent, true, STREAM_CRYPTO_METHOD_TLS_SERVER)); + +$client = stream_socket_client("tcp://$addr", $errno, $errstr, 5, STREAM_CLIENT_CONNECT, + stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'alpn_protocols' => 'h2', + ]])); +$child = stream_socket_accept($server); +stream_set_blocking($child, false); +var_dump(stream_socket_enable_crypto($child, true, STREAM_CRYPTO_METHOD_TLS_SERVER, $parent)); + +fclose($parent); +fclose($parentClient); + +stream_set_blocking($client, false); +$clientDone = $childDone = 0; +for ($i = 0; $i < 1000 && ($clientDone !== true || $childDone !== true); $i++) { + if ($clientDone !== true) { + $clientDone = stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + } + if ($childDone !== true) { + $childDone = stream_socket_enable_crypto($child, true, STREAM_CRYPTO_METHOD_TLS_SERVER); + } + usleep(1000); +} +var_dump($clientDone, $childDone); +var_dump(stream_get_meta_data($client)['crypto']['alpn_protocol']); +?> +--CLEAN-- + +--EXPECT-- +int(0) +int(0) +bool(true) +bool(true) +string(2) "h2" diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index dcde68176b8b..bda11db924fc 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -785,7 +785,8 @@ static int php_openssl_win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx, bool is_self_signed = false; - stream = (php_stream*)arg; + SSL *ssl = X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + stream = (php_stream*)SSL_get_ex_data(ssl, php_openssl_get_ssl_stream_data_index()); sslsock = (php_openssl_netstream_data_t*)stream->abstract; { /* First convert the x509 struct back to a DER encoded buffer and let Windows decode it into a form it can work with */ @@ -1020,7 +1021,7 @@ static zend_result php_openssl_enable_peer_verification(SSL_CTX *ctx, php_stream } } else { #ifdef PHP_WIN32 - SSL_CTX_set_cert_verify_callback(ctx, php_openssl_win_cert_verify_callback, (void *)stream); + SSL_CTX_set_cert_verify_callback(ctx, php_openssl_win_cert_verify_callback, NULL); #else if (sslsock->is_client && !SSL_CTX_set_default_verify_paths(ctx)) { php_stream_warn(stream, ProtocolError, @@ -1649,7 +1650,10 @@ static int php_openssl_server_alpn_callback(SSL *ssl_handle, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) /* {{{ */ { - php_openssl_netstream_data_t *sslsock = arg; + php_stream *stream = (php_stream *)SSL_get_ex_data(ssl_handle, php_openssl_get_ssl_stream_data_index()); + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t *)stream->abstract; + + ZEND_ASSERT(sslsock->alpn_ctx.data != NULL); if (SSL_select_next_proto((unsigned char **)out, outlen, sslsock->alpn_ctx.data, sslsock->alpn_ctx.len, in, inlen) != OPENSSL_NPN_NEGOTIATED) { return SSL_TLSEXT_ERR_NOACK; @@ -2707,7 +2711,7 @@ static zend_result php_openssl_create_server_ctx(php_stream *stream, } else { sslsock->alpn_ctx.data = (unsigned char *) pestrndup((const char*)alpn, alpn_len, php_stream_is_persistent(stream)); sslsock->alpn_ctx.len = alpn_len; - SSL_CTX_set_alpn_select_cb(sslsock->ctx, php_openssl_server_alpn_callback, sslsock); + SSL_CTX_set_alpn_select_cb(sslsock->ctx, php_openssl_server_alpn_callback, NULL); } efree(alpn); @@ -2808,6 +2812,16 @@ static zend_result php_openssl_setup_crypto(php_stream *stream, } else { SSL_CTX_up_ref(parent_sslsock->ctx); sslsock->ctx = parent_sslsock->ctx; +#ifdef HAVE_TLS_ALPN + if (parent_sslsock->alpn_ctx.data) { + if (sslsock->alpn_ctx.data) { + pefree(sslsock->alpn_ctx.data, php_stream_is_persistent(stream)); + } + sslsock->alpn_ctx.data = (unsigned char *) pestrndup((const char *) parent_sslsock->alpn_ctx.data, + parent_sslsock->alpn_ctx.len, php_stream_is_persistent(stream)); + sslsock->alpn_ctx.len = parent_sslsock->alpn_ctx.len; + } +#endif if (parent_sslsock->session_callbacks) { parent_sslsock->session_callbacks->refcount++; sslsock->session_callbacks = parent_sslsock->session_callbacks;