Commit 1e8be52670 for openssl.org
commit 1e8be52670ec6e26fe1066690909a0529237c5f3
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Thu Aug 13 11:14:29 2026 +0900
QUIC: pass the user SSL to session ID callbacks
ssl_generate_session_id() selected and invoked GEN_SESSION_CB using the
internal handshake-layer SSL. This is the same object as the application
SSL for TLS and DTLS, but not for QUIC, where user_ssl identifies the
public QUIC connection.
As a result, a custom callback received an SSL it had never seen, so
per-SSL state such as ex_data was unavailable. The reader also locked the
inner SSL, whereas SSL_set_generate_session_id() locks the public object
before updating the callback pointer.
Use the user SSL for the per-connection lock and pass it to every selected
session ID callback. In def_generate_session_id(), unwrap it with
SSL_CONNECTION_FROM_SSL() when obtaining the RAND library context. This
preserves the handshake SSL_CTX semantics needed by SSL_listen_ex() when
the public and handshake contexts differ.
Test the callback during ordinary TLS 1.3 QUIC ticket generation and
verify that every invocation receives the public SSL and its ex_data.
Fixes #32360
Assisted-by: Grok:grok-4.6
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Merge-date: Wed Nov 4 17:41:48 2026
Merged-from: https://github.com/openssl/openssl/pull/32361
diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
index 99d35f5f89..2279851e86 100644
--- a/ssl/ssl_sess.c
+++ b/ssl/ssl_sess.c
@@ -333,7 +333,9 @@ static int def_generate_session_id(SSL *ssl, unsigned char *id,
unsigned int retry = 0;
do {
- if (RAND_bytes_ex(ssl->ctx->libctx, id, *id_len, 0) <= 0)
+ if (RAND_bytes_ex(SSL_CONNECTION_GET_CTX(SSL_CONNECTION_FROM_SSL(ssl))->libctx,
+ id, *id_len, 0)
+ <= 0)
return 0;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (retry > 0) {
@@ -363,7 +365,7 @@ int ssl_generate_session_id(SSL_CONNECTION *s, SSL_SESSION *ss)
int match;
unsigned int tmp;
GEN_SESSION_CB cb = def_generate_session_id;
- SSL *ssl = SSL_CONNECTION_GET_SSL(s);
+ SSL *ssl = SSL_CONNECTION_GET_USER_SSL(s);
switch (s->version) {
case TLS1_VERSION:
@@ -402,7 +404,7 @@ int ssl_generate_session_id(SSL_CONNECTION *s, SSL_SESSION *ss)
}
/* Choose which callback will set the session ID */
- if (!CRYPTO_THREAD_read_lock(SSL_CONNECTION_GET_SSL(s)->lock)) {
+ if (!CRYPTO_THREAD_read_lock(ssl->lock)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
diff --git a/test/quicapitest.c b/test/quicapitest.c
index e533dfde8e..f4b860dda9 100644
--- a/test/quicapitest.c
+++ b/test/quicapitest.c
@@ -2466,6 +2466,111 @@ err:
return testresult;
}
+static SSL *gen_sid_expected_ssl = NULL;
+static int gen_sid_called = 0;
+static int gen_sid_arg_error = 0;
+static int gen_sid_idx = -1;
+static const char gen_sid_marker[] = "gen-sid-marker";
+
+static int generate_session_id_user_ssl(SSL *ssl, unsigned char *id,
+ unsigned int *id_len)
+{
+ unsigned int i;
+
+ gen_sid_called++;
+ if (ssl != gen_sid_expected_ssl
+ || !SSL_is_quic(ssl)
+ || SSL_get_ex_data(ssl, gen_sid_idx) != (void *)gen_sid_marker) {
+ gen_sid_arg_error = 1;
+ return 0;
+ }
+
+ if (*id_len == 0)
+ return 0;
+ for (i = 0; i < *id_len; i++)
+ id[i] = (unsigned char)(0xa0 + i);
+ return !SSL_has_matching_session_id(ssl, id, *id_len);
+}
+
+/*
+ * generate_session_id must be invoked with the SSL the application holds,
+ * not the inner handshake-layer object. Ordinary TLS 1.3 QUIC handshake
+ * tickets reach the callback after tls_finish_handshake() clears
+ * ticket_expected; no explicit extra ticket is required.
+ */
+static int test_generate_session_id_user_ssl(void)
+{
+ SSL_CTX *cctx = NULL, *sctx = NULL;
+ SSL *clientssl = NULL, *serverssl = NULL, *qlistener = NULL;
+ int testresult = 0;
+ int ret, i;
+
+ if (gen_sid_idx < 0) {
+ gen_sid_idx = SSL_get_ex_new_index(0, "gen sid", NULL, NULL, NULL);
+ if (!TEST_int_ge(gen_sid_idx, 0))
+ return 0;
+ }
+
+ gen_sid_called = 0;
+ gen_sid_arg_error = 0;
+ gen_sid_expected_ssl = NULL;
+
+ if (!TEST_ptr(sctx = create_server_ctx())
+ || !TEST_ptr(cctx = create_client_ctx()))
+ goto err;
+
+ if (!create_quic_ssl_objects(sctx, cctx, &qlistener, &clientssl))
+ goto err;
+
+ /* Send ClientHello and server retry */
+ for (i = 0; i < 2; i++) {
+ ret = SSL_connect(clientssl);
+ if (!TEST_int_le(ret, 0)
+ || !TEST_int_eq(SSL_get_error(clientssl, ret), SSL_ERROR_WANT_READ))
+ goto err;
+ SSL_handle_events(qlistener);
+ }
+
+ serverssl = SSL_accept_connection(qlistener, 0);
+ if (!TEST_ptr(serverssl) || !TEST_false(SSL_is_init_finished(serverssl)))
+ goto err;
+
+ gen_sid_expected_ssl = serverssl;
+ if (!TEST_true(SSL_set_ex_data(serverssl, gen_sid_idx,
+ (void *)gen_sid_marker))
+ || !TEST_true(SSL_set_generate_session_id(serverssl,
+ generate_session_id_user_ssl)))
+ goto err;
+
+ if (!TEST_true(create_bare_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE, 0, 0)))
+ goto err;
+
+ /*
+ * Ordinary TLS 1.3 tickets are constructed after the handshake finishes.
+ * Drive both endpoints until the callback has run; do not request an
+ * extra ticket.
+ */
+ for (i = 0; i < 20 && gen_sid_called == 0; i++) {
+ if (!TEST_true(SSL_handle_events(serverssl))
+ || !TEST_true(SSL_handle_events(clientssl)))
+ goto err;
+ }
+
+ if (!TEST_int_gt(gen_sid_called, 0) || !TEST_int_eq(gen_sid_arg_error, 0))
+ goto err;
+
+ testresult = 1;
+err:
+ gen_sid_expected_ssl = NULL;
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_free(qlistener);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
static int test_domain_flags(void)
{
int testresult = 0;
@@ -5423,6 +5528,7 @@ int setup_tests(void)
ADD_TEST(test_get_shutdown);
ADD_ALL_TESTS(test_tparam, OSSL_NELEM(tparam_tests));
ADD_TEST(test_session_cb);
+ ADD_TEST(test_generate_session_id_user_ssl);
ADD_TEST(test_domain_flags);
ADD_TEST(test_early_ticks);
ADD_TEST(test_ssl_new_from_listener);