Commit f31510e953 for openssl.org
commit f31510e95333b33a8765cbb81a147df7572c88b2
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date: Tue Jun 23 16:32:12 2026 +0200
quic: add mfail test for handshake multi-packet processing
This tests handshake level phase using mfail covering SSL_do_handshake.
It is a test for #31323.
Assisted-by: Claude:claude-opus-4-8
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Neil Horman <nhorman@openssl.org>
MergeDate: Sat Jul 4 16:57:38 2026
(Merged from https://github.com/openssl/openssl/pull/31324)
diff --git a/test/quicapitest.c b/test/quicapitest.c
index ff858fb170..271778f6b3 100644
--- a/test/quicapitest.c
+++ b/test/quicapitest.c
@@ -2798,8 +2798,16 @@ end:
return ret;
}
-static int create_quic_ssl_objects(SSL_CTX *sctx, SSL_CTX *cctx,
- SSL **lssl, SSL **cssl)
+/* Fake clock for tests that advance QUIC time without consuming real time. */
+static OSSL_TIME fake_now;
+
+static OSSL_TIME fake_now_cb(void *arg)
+{
+ return fake_now;
+}
+
+static int create_quic_ssl_objects_ex(SSL_CTX *sctx, SSL_CTX *cctx,
+ SSL **lssl, SSL **cssl, int use_fake_time)
{
BIO_ADDR *addr = NULL;
struct in_addr ina;
@@ -2840,6 +2848,18 @@ static int create_quic_ssl_objects(SSL_CTX *sctx, SSL_CTX *cctx,
SSL_set_bio(*cssl, cbio, cbio);
cbio = NULL;
+ if (use_fake_time) {
+ /*
+ * The base value does not matter but must be nonzero, as the ACK
+ * manager reads a zero packet timestamp as unset. Use real time to
+ * match the clock the engines were created with.
+ */
+ fake_now = ossl_time_now();
+ if (!TEST_true(ossl_quic_set_override_now_cb(*lssl, fake_now_cb, NULL))
+ || !TEST_true(ossl_quic_set_override_now_cb(*cssl, fake_now_cb, NULL)))
+ goto err;
+ }
+
ret = 1;
err:
@@ -2855,6 +2875,12 @@ err:
return ret;
}
+static int create_quic_ssl_objects(SSL_CTX *sctx, SSL_CTX *cctx,
+ SSL **lssl, SSL **cssl)
+{
+ return create_quic_ssl_objects_ex(sctx, cctx, lssl, cssl, 0);
+}
+
static int test_ssl_client_as_ossl_quic_method(void)
{
SSL_CTX *cctx = NULL, *sctx = NULL;
@@ -3494,6 +3520,146 @@ static int test_quic_peer_addr_v6(void)
}
#endif
+#ifndef OPENSSL_NO_CACHED_FETCH
+/*
+ * Advance the fake clock to the next QUIC timer event when both endpoints are
+ * idle, consuming no real time.
+ */
+static void quic_advance_time(SSL *clientssl, SSL *serverssl)
+{
+ struct timeval tv;
+ int inf = 0;
+ OSSL_TIME delay = ossl_time_infinite(), t;
+
+ /* If there is data waiting to be processed, do not wait - tick instead. */
+ if (BIO_pending(SSL_get_rbio(clientssl)) > 0)
+ return;
+
+ if (SSL_get_event_timeout(clientssl, &tv, &inf) && !inf) {
+ t = ossl_time_from_timeval(tv);
+ if (ossl_time_compare(t, delay) < 0)
+ delay = t;
+ }
+ if (SSL_get_event_timeout(serverssl, &tv, &inf) && !inf) {
+ t = ossl_time_from_timeval(tv);
+ if (ossl_time_compare(t, delay) < 0)
+ delay = t;
+ }
+
+ if (!ossl_time_is_infinite(delay))
+ fake_now = ossl_time_add(fake_now, delay);
+}
+
+static int test_quic_handshake_multipkt_mfail(void)
+{
+ SSL_CTX *cctx = NULL, *sctx = NULL;
+ SSL *clientssl = NULL, *serverssl = NULL, *qlistener = NULL;
+ QUIC_CHANNEL *sch = NULL, *cch = NULL;
+ int ret = 0, rc = 0, err, i;
+
+ if (!TEST_ptr(sctx = create_server_ctx())
+ || !TEST_ptr(cctx = create_client_ctx()))
+ goto err;
+
+ if (!create_quic_ssl_objects_ex(sctx, cctx, &qlistener, &clientssl, 1))
+ goto err;
+
+ if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "localhost")))
+ goto err;
+
+ /* Get the listener to bind a channel we can accept. */
+ for (i = 0; i < 10; i++) {
+ rc = SSL_connect(clientssl);
+ if (rc <= 0) {
+ err = SSL_get_error(clientssl, rc);
+ if (!TEST_true(err == SSL_ERROR_WANT_READ
+ || err == SSL_ERROR_WANT_WRITE))
+ goto err;
+ }
+ SSL_handle_events(qlistener);
+
+ serverssl = SSL_accept_connection(qlistener, 0);
+ if (serverssl != NULL)
+ break;
+ }
+ if (!TEST_ptr(serverssl)
+ || !TEST_false(SSL_is_init_finished(serverssl)))
+ goto err;
+
+ if (!TEST_ptr(sch = ossl_quic_conn_get_channel(serverssl)))
+ goto err;
+
+ /* Do handshake until the server reaches the first flight. */
+ for (i = 0; i < 10; i++) {
+ rc = SSL_do_handshake(clientssl);
+ if (rc <= 0) {
+ err = SSL_get_error(clientssl, rc);
+ if (!TEST_true(err == SSL_ERROR_WANT_READ
+ || err == SSL_ERROR_WANT_WRITE))
+ goto err;
+ }
+ if (ossl_quic_channel_is_term_any(sch))
+ goto err;
+ SSL_handle_events(serverssl);
+ if (sch->tx_enc_level >= QUIC_ENC_LEVEL_HANDSHAKE)
+ break;
+ quic_advance_time(clientssl, serverssl);
+ }
+ if (!TEST_int_lt(i, 10))
+ goto err;
+
+ /* Process the multi-packet datagram under mfail. */
+ MFAIL_start();
+ rc = SSL_do_handshake(clientssl);
+ MFAIL_end();
+
+ /* A fatal injected failure may terminate the connection - bail if so. */
+ if (rc <= 0) {
+ err = SSL_get_error(clientssl, rc);
+ if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE)
+ goto err;
+ }
+
+ if (!TEST_ptr(cch = ossl_quic_conn_get_channel(clientssl)))
+ goto err;
+
+ /* Connection still live so get the handshake to converge. */
+ for (i = 0; i < 10; i++) {
+ rc = SSL_do_handshake(clientssl);
+ if (rc == 1)
+ break;
+
+ err = SSL_get_error(clientssl, rc);
+ if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
+ ret = -1;
+ goto err;
+ }
+
+ if (ossl_quic_channel_is_term_any(cch)
+ || ossl_quic_channel_is_term_any(sch))
+ goto err;
+
+ SSL_handle_events(serverssl);
+ quic_advance_time(clientssl, serverssl);
+ }
+ if (!TEST_int_lt(i, 10)) {
+ ret = -1;
+ goto err;
+ }
+
+ ret = 1;
+
+err:
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_free(qlistener);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+
+ return ret;
+}
+#endif
+
/* Test ECH with quic */
static int test_ech(void)
{
@@ -3789,6 +3955,9 @@ int setup_tests(void)
ADD_TEST(test_quic_peer_addr_v6);
#endif
ADD_TEST(test_quic_peer_addr_v4);
+#ifndef OPENSSL_NO_CACHED_FETCH
+ ADD_MFAIL_NO_CHECK_TEST(test_quic_handshake_multipkt_mfail);
+#endif
ADD_TEST(test_ech);
ADD_TEST(test_quic_resize_txe);
#ifdef OPENSSL_NO_CACHED_FETCH