Add a test for QUIC non IO retry errors
authorMatt Caswell <matt@openssl.org>
Thu, 31 Aug 2023 15:18:28 +0000 (16:18 +0100)
committerHugo Landau <hlandau@openssl.org>
Sat, 2 Sep 2023 14:23:55 +0000 (15:23 +0100)
Test that errors such as SSL_ERROR_WANT_RETRY_VERIFY are properly
handled by QUIC connections.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21922)

doc/designs/quic-design/quic-fault-injector.md
test/helpers/quictestlib.c
test/helpers/quictestlib.h
test/quicapitest.c

index a60763518f02a8b5a93781dfbc3ea149d8eb5035..30db905ee84a6a7e3275e258eec7c598ef35ff4c 100644 (file)
@@ -228,6 +228,13 @@ void ossl_quic_fault_free(OSSL_QUIC_FAULT *fault);
  */
 int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl);
 
+/*
+ * Same as qtest_create_quic_connection but will stop (successfully) if the
+ * clientssl indicates SSL_ERROR_WANT_XXX as specified by |wanterr|
+ */
+int qtest_create_quic_connection_ex(QUIC_TSERVER *qtserv, SSL *clientssl,
+                                    int wanterr);
+
 /*
  * Confirm that the server has received the given transport error code.
  */
index 2dbbb435bbabb9ff24800c1e3124faeb9a45bab4..2fcb4bdb6f83e0773777333c1d821a2f46fdf3e6 100644 (file)
@@ -239,6 +239,7 @@ int qtest_supports_blocking(void)
 
 #if defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG)
 static int globserverret = 0;
+static TSAN_QUALIFIER int abortserverthread = 0;
 static QUIC_TSERVER *globtserv;
 static const thread_t thread_zero;
 
@@ -253,7 +254,8 @@ static void run_server_thread(void)
 }
 #endif
 
-int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
+int qtest_create_quic_connection_ex(QUIC_TSERVER *qtserv, SSL *clientssl,
+                                    int wanterr)
 {
     int retc = -1, rets = 0, abortctr = 0, ret = 0;
     int clienterr = 0, servererr = 0;
@@ -263,6 +265,9 @@ int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
      * t uninitialised
      */
     thread_t t = thread_zero;
+
+    if (clientssl != NULL)
+        abortserverthread = 0;
 #endif
 
     if (!TEST_ptr(qtserv)) {
@@ -295,10 +300,21 @@ int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
             if (retc <= 0) {
                 err = SSL_get_error(clientssl, retc);
 
-                if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
-                    TEST_info("SSL_connect() failed %d, %d", retc, err);
-                    TEST_openssl_errors();
-                    clienterr = 1;
+                if (err == wanterr) {
+                    retc = 1;
+#if defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG)
+                    if (qtserv == NULL && rets > 0)
+                        tsan_store(&abortserverthread, 1);
+                    else
+#endif
+                        rets = 1;
+                } else {
+                    if (err != SSL_ERROR_WANT_READ
+                            && err != SSL_ERROR_WANT_WRITE) {
+                        TEST_info("SSL_connect() failed %d, %d", retc, err);
+                        TEST_openssl_errors();
+                        clienterr = 1;
+                    }
                 }
             }
         }
@@ -312,6 +328,7 @@ int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
          */
         if (!clienterr && retc <= 0)
             SSL_handle_events(clientssl);
+
         if (!servererr && rets <= 0) {
             qtest_add_time(1);
             ossl_quic_tserver_tick(qtserv);
@@ -327,7 +344,12 @@ int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
             TEST_info("No progress made");
             goto err;
         }
-    } while ((retc <= 0 && !clienterr) || (rets <= 0 && !servererr));
+    } while ((retc <= 0 && !clienterr)
+             || (rets <= 0 && !servererr
+#if defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG)
+                 && !tsan_load(&abortserverthread)
+#endif
+                ));
 
     if (qtserv == NULL && rets > 0) {
 #if defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG)
@@ -345,6 +367,11 @@ int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
     return ret;
 }
 
+int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl)
+{
+    return qtest_create_quic_connection_ex(qtserv, clientssl, SSL_ERROR_NONE);
+}
+
 #if defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG)
 static TSAN_QUALIFIER int shutdowndone;
 
index cfda1b29b5fb904ae3c11184c568aeaf8307dea5..fb1c5d88b534de5bdc741c37ee39ce01f69644c8 100644 (file)
@@ -62,6 +62,13 @@ int qtest_supports_blocking(void);
  */
 int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl);
 
+/*
+ * Same as qtest_create_quic_connection but will stop (successfully) if the
+ * clientssl indicates SSL_ERROR_WANT_XXX as specified by |wanterr|
+ */
+int qtest_create_quic_connection_ex(QUIC_TSERVER *qtserv, SSL *clientssl,
+                                    int wanterr);
+
 /*
  * Shutdown the client SSL object gracefully
  */
index 83a048bc74480a2ef48f557fa50a89a432cc9069..5eff924527ee65394c4002bced74b79c83db2194 100644 (file)
@@ -1003,6 +1003,64 @@ static int test_multiple_dgrams(void)
     return testresult;
 }
 
+static int non_io_retry_cert_verify_cb(X509_STORE_CTX *ctx, void *arg)
+{
+    int idx = SSL_get_ex_data_X509_STORE_CTX_idx();
+    SSL *ssl;
+    int *ctr = (int *)arg;
+
+    /* this should not happen but check anyway */
+    if (idx < 0
+        || (ssl = X509_STORE_CTX_get_ex_data(ctx, idx)) == NULL)
+        return 0;
+
+    /* If this is the first time we've been called then retry */
+    if (((*ctr)++) == 0)
+        return SSL_set_retry_verify(ssl);
+
+    /* Otherwise do nothing - verification succeeds. Continue as normal */
+    return 1;
+}
+
+/* Test that we can handle a non-io related retry error
+ * Test 0: Non-blocking
+ * Test 1: Blocking
+ */
+static int test_non_io_retry(int idx)
+{
+    SSL_CTX *cctx;
+    SSL *clientquic = NULL;
+    QUIC_TSERVER *qtserv = NULL;
+    int testresult = 0;
+    int flags = 0, ctr = 0;
+
+    if (idx >= 1 && !qtest_supports_blocking())
+        return TEST_skip("Blocking tests not supported in this build");
+
+    cctx = SSL_CTX_new_ex(libctx, NULL, OSSL_QUIC_client_method());
+    if (!TEST_ptr(cctx))
+        goto err;
+
+    SSL_CTX_set_cert_verify_callback(cctx, non_io_retry_cert_verify_cb, &ctr);
+
+    flags = (idx >= 1) ? QTEST_FLAG_BLOCK : 0;
+    if (!TEST_true(qtest_create_quic_objects(libctx, cctx, NULL, cert, privkey,
+                                             flags, &qtserv, &clientquic, NULL))
+            || !TEST_true(qtest_create_quic_connection_ex(qtserv, clientquic,
+                            SSL_ERROR_WANT_RETRY_VERIFY))
+            || !TEST_int_eq(SSL_want(clientquic), SSL_RETRY_VERIFY)
+            || !TEST_true(qtest_create_quic_connection(qtserv, clientquic)))
+        goto err;
+
+    testresult = 1;
+ err:
+    SSL_free(clientquic);
+    ossl_quic_tserver_free(qtserv);
+    SSL_CTX_free(cctx);
+
+    return testresult;
+}
+
 OPT_TEST_DECLARE_USAGE("provider config certsdir datadir\n")
 
 int setup_tests(void)
@@ -1072,6 +1130,7 @@ int setup_tests(void)
     ADD_TEST(test_bio_ssl);
     ADD_TEST(test_back_pressure);
     ADD_TEST(test_multiple_dgrams);
+    ADD_ALL_TESTS(test_non_io_retry, 2);
     return 1;
  err:
     cleanup_tests();