From cf1f5d212c9d30d2a1996b7426cf8483205e8491 Mon Sep 17 00:00:00 2001
From: Matt Caswell <matt@openssl.org>
Date: Tue, 5 Mar 2024 15:35:51 +0000
Subject: [PATCH] Extend the multi_resume test for simultaneous resumptions

Test what happens if the same session gets resumed multiple times at the
same time - and one of them gets marked as not_resumable.

Related to CVE-2024-2511

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/24044)

(cherry picked from commit 031b11a4054c972a5e2f07dfa81ce1842453253e)
Signed-off-by: Liu-Ermeng <liuermeng2@huawei.com>
---
 test/sslapitest.c | 89 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 85 insertions(+), 4 deletions(-)

diff --git a/test/sslapitest.c b/test/sslapitest.c
index 2992356fdf..472b1224ca 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -7288,12 +7288,63 @@ static int test_inherit_verify_param(void)
     return testresult;
 }
 
+struct resume_servername_cb_data {
+    int i;
+    SSL_CTX *cctx;
+    SSL_CTX *sctx;
+    SSL_SESSION *sess;
+    int recurse;
+};
+
+/*
+ * Servername callback. We use it here to run another complete handshake using
+ * the same session - and mark the session as not_resuamble at the end
+ */
+static int resume_servername_cb(SSL *s, int *ad, void *arg)
+{
+    struct resume_servername_cb_data *cbdata = arg;
+    SSL *serverssl = NULL, *clientssl = NULL;
+    int ret = SSL_TLSEXT_ERR_ALERT_FATAL;
+
+    if (cbdata->recurse)
+        return SSL_TLSEXT_ERR_ALERT_FATAL;
+
+    if ((cbdata->i % 3) != 1)
+        return SSL_TLSEXT_ERR_OK;
+
+    cbdata->recurse = 1;
+
+    if (!TEST_true(create_ssl_objects(cbdata->sctx, cbdata->cctx, &serverssl,
+                                      &clientssl, NULL, NULL))
+            || !TEST_true(SSL_set_session(clientssl, cbdata->sess)))
+        goto end;
+
+    ERR_set_mark();
+    /*
+     * We expect this to fail - because the servername cb will fail. This will
+     * mark the session as not_resumable.
+     */
+    if (!TEST_false(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))) {
+        ERR_clear_last_mark();
+        goto end;
+    }
+    ERR_pop_to_mark();
+
+    ret = SSL_TLSEXT_ERR_OK;
+ end:
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    cbdata->recurse = 0;
+    return ret;
+}
+
 /*
  * Test multiple resumptions and cache size handling
  * Test 0: TLSv1.3 (max_early_data set)
  * Test 1: TLSv1.3 (SSL_OP_NO_TICKET set)
  * Test 2: TLSv1.3 (max_early_data and SSL_OP_NO_TICKET set)
- * Test 3: TLSv1.2
+ * Test 3: TLSv1.3 (SSL_OP_NO_TICKET, simultaneous resumes)
+ * Test 4: TLSv1.2
  */
 static int test_multi_resume(int idx)
 {
@@ -7302,9 +7353,19 @@ static int test_multi_resume(int idx)
     SSL_SESSION *sess = NULL;
     int max_version = TLS1_3_VERSION;
     int i, testresult = 0;
+    struct resume_servername_cb_data cbdata;
 
-    if (idx == 3)
+#if defined(OPENSSL_NO_TLS1_2)
+    if (idx == 4)
+        return TEST_skip("TLSv1.2 is disabled in this build");
+#else
+    if (idx == 4)
         max_version = TLS1_2_VERSION;
+#endif
+#if defined(OSSL_NO_USABLE_TLS1_3)
+    if (idx != 4)
+        return TEST_skip("No usable TLSv1.3 in this build");
+#endif
 
     if (!TEST_true(create_ssl_ctx_pair(TLS_server_method(),
                                        TLS_client_method(), TLS1_VERSION,
@@ -7320,17 +7381,37 @@ static int test_multi_resume(int idx)
         if (!TEST_true(SSL_CTX_set_max_early_data(sctx, 1024)))
             goto end;
     }
-    if (idx == 1 || idx == 2)
+    if (idx == 1 || idx == 2 || idx == 3)
         SSL_CTX_set_options(sctx, SSL_OP_NO_TICKET);
 
     SSL_CTX_sess_set_cache_size(sctx, 5);
 
+    if (idx == 3) {
+        SSL_CTX_set_tlsext_servername_callback(sctx, resume_servername_cb);
+        SSL_CTX_set_tlsext_servername_arg(sctx, &cbdata);
+        cbdata.cctx = cctx;
+        cbdata.sctx = sctx;
+        cbdata.recurse = 0;
+    }
+
     for (i = 0; i < 30; i++) {
         if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
                                                 NULL, NULL))
                 || !TEST_true(SSL_set_session(clientssl, sess)))
             goto end;
 
+        /*
+         * Check simultaneous resumes. We pause the connection part way through
+         * the handshake by (mis)using the servername_cb. The pause occurs after
+         * session resumption has already occurred, but before any session
+         * tickets have been issued. While paused we run another complete
+         * handshake resuming the same session.
+         */
+        if (idx == 3) {
+            cbdata.i = i;
+            cbdata.sess = sess;
+        }
+
         /*
          * Recreate a bug where dynamically changing the max_early_data value
          * can cause sessions in the session cache which cannot be deleted.
@@ -7513,7 +7594,7 @@ int setup_tests(void)
 #if !defined(OPENSSL_NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_3)
     ADD_ALL_TESTS(test_serverinfo_custom, 4);
 #endif
-    ADD_ALL_TESTS(test_multi_resume, 4);
+    ADD_ALL_TESTS(test_multi_resume, 5);
     return 1;
 }
 
-- 
2.33.0

