Commit e6f5ed33f1 for openssl.org
commit e6f5ed33f143aa4aab38172f3dcc160e2ef71c52
Author: Weidong Wang <kenazcharisma@gmail.com>
Date: Wed Mar 18 06:00:06 2026 -0500
Add test for SSL_SESSION leak on ticket abort in tls_parse_ctos_psk()
Add test_ticket_abort_session_leak() to verify that returning
SSL_TICKET_RETURN_ABORT from the decrypt ticket callback during TLS 1.3
resumption does not leak the SSL_SESSION allocated by tls_decrypt_ticket().
This exercises the error path fixed in commit 96f424c439.
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
MergeDate: Wed May 20 14:38:04 2026
(Merged from https://github.com/openssl/openssl/pull/30464)
diff --git a/test/sslapitest.c b/test/sslapitest.c
index b17318dfbe..2cf22bd9b8 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -9309,6 +9309,106 @@ end:
return testresult;
}
+/*
+ * Callback that always returns ABORT for successfully decrypted tickets.
+ * Used by test_ticket_abort_session_leak to exercise the error path in
+ * tls_parse_ctos_psk() that previously leaked the SSL_SESSION.
+ */
+static SSL_TICKET_RETURN dec_tick_abort_cb(SSL *s, SSL_SESSION *ss,
+ const unsigned char *keyname,
+ size_t keyname_length,
+ SSL_TICKET_STATUS status,
+ void *arg)
+{
+ if (status == SSL_TICKET_SUCCESS || status == SSL_TICKET_SUCCESS_RENEW)
+ return SSL_TICKET_RETURN_ABORT;
+
+ return SSL_TICKET_RETURN_IGNORE_RENEW;
+}
+
+/*
+ * Test that returning SSL_TICKET_RETURN_ABORT from the decrypt ticket callback
+ * during TLS 1.3 resumption does not leak the SSL_SESSION allocated by
+ * tls_decrypt_ticket(). Before the fix, tls_parse_ctos_psk() would execute a
+ * bare "return 0" instead of "goto err", bypassing SSL_SESSION_free(sess).
+ * When run under LeakSanitizer the leaked session will be reported.
+ */
+static int test_ticket_abort_session_leak(void)
+{
+ SSL_CTX *cctx = NULL, *sctx = NULL;
+ SSL *clientssl = NULL, *serverssl = NULL;
+ SSL_SESSION *clntsess = NULL;
+ int testresult = 0;
+
+#ifdef OSSL_NO_USABLE_TLS1_3
+ return 1;
+#endif
+
+ if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+ TLS_client_method(),
+ TLS1_3_VERSION, TLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+
+ if (!TEST_true(SSL_CTX_set_session_cache_mode(sctx, SSL_SESS_CACHE_OFF)))
+ goto end;
+
+ /* First handshake: use the normal gen/dec callbacks to get a ticket */
+ if (!TEST_true(SSL_CTX_set_session_ticket_cb(sctx, gen_tick_cb, dec_tick_cb,
+ NULL)))
+ goto end;
+
+ gen_tick_called = dec_tick_called = tick_key_cb_called = 0;
+ tick_dec_ret = SSL_TICKET_RETURN_USE_RENEW;
+
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+ NULL, NULL))
+ || !TEST_true(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE)))
+ goto end;
+
+ clntsess = SSL_get1_session(clientssl);
+ if (!TEST_ptr(clntsess))
+ goto end;
+
+ SSL_shutdown(clientssl);
+ SSL_shutdown(serverssl);
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ serverssl = clientssl = NULL;
+
+ /*
+ * Second handshake (resumption): switch to the abort callback.
+ * The server will decrypt the ticket, allocate an SSL_SESSION, then the
+ * callback returns ABORT. The handshake must fail, and the session
+ * allocated inside tls_decrypt_ticket() must be freed (not leaked).
+ */
+ if (!TEST_true(SSL_CTX_set_session_ticket_cb(sctx, gen_tick_cb,
+ dec_tick_abort_cb, NULL)))
+ goto end;
+
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+ NULL, NULL))
+ || !TEST_true(SSL_set_session(clientssl, clntsess)))
+ goto end;
+
+ /* Resumption should fail because the callback aborts */
+ if (!TEST_false(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_SSL)))
+ goto end;
+
+ testresult = 1;
+
+end:
+ SSL_SESSION_free(clntsess);
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+
+ return testresult;
+}
+
/*
* Test incorrect shutdown.
* Test 0: client does not shutdown properly,
@@ -14947,6 +15047,7 @@ int setup_tests(void)
ADD_ALL_TESTS(test_ssl_pending, 2);
ADD_ALL_TESTS(test_ssl_get_shared_ciphers, OSSL_NELEM(shared_ciphers_data));
ADD_ALL_TESTS(test_ticket_callbacks, 20);
+ ADD_TEST(test_ticket_abort_session_leak);
ADD_ALL_TESTS(test_shutdown, 7);
ADD_TEST(test_async_shutdown);
ADD_ALL_TESTS(test_ssl_bio_eof, 2);