Commit 0966e103eb for openssl.org

commit 0966e103ebf36b48c51c384e1954093536df2a63
Author: Matt Caswell <matt@openssl.foundation>
Date:   Mon Oct 19 13:19:38 2026 +0100

    DTLS listener: free a pending connection that fails after HVR

    dtls_listener_conn_needs_retry() reported a DTLS 1.2 connection as
    waiting for its second ClientHello whenever its handshake state was
    DTLS_ST_SW_HELLO_VERIFY_REQUEST, even if the connection had since
    entered the error state, e.g. on receiving a fatal alert from the peer.
    The listener then kept it in its pending table, and called SSL_accept()
    on it every tick, until the pending timeout expired instead of freeing
    it as a failed connection.

    Check ossl_statem_in_error() as the DTLS 1.3 HRR case already does.

    Assisted-by: Claude:claude-opus-5-5
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    Reviewed-by: Ryan Hooper <ryanh@openssl.foundation>
    Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
    Merge-date: Thu Oct 22 14:38:07 2026
    Merged-from: https://github.com/openssl/openssl/pull/33265

diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index d4240b29f9..09c961742e 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -2125,7 +2125,8 @@ static int dtls_listener_conn_needs_retry(SSL *ssl)
      * For DTLS 1.2: Check if we're in a state that indicates HVR was sent.
      * The state machine will be waiting for the next ClientHello.
      */
-    if (sc->statem.hand_state == DTLS_ST_SW_HELLO_VERIFY_REQUEST)
+    if (sc->statem.hand_state == DTLS_ST_SW_HELLO_VERIFY_REQUEST
+        && !ossl_statem_in_error(sc))
         return 1;

     return 0;
diff --git a/test/dtlsssllistenertest.c b/test/dtlsssllistenertest.c
index 95ed2e2eda..f6d98bf941 100644
--- a/test/dtlsssllistenertest.c
+++ b/test/dtlsssllistenertest.c
@@ -7509,6 +7509,104 @@ static int test_dtls_listener_flood_then_handshake(void)
 #endif
     return ok;
 }
+
+#ifndef OPENSSL_NO_DTLS1_2
+static void get_pending_conn_cb(SSL *ssl, const BIO_ADDR *peer, void *arg)
+{
+    *(SSL **)arg = ssl;
+}
+
+/* Has the listener's single pending connection sent its HVR? */
+static int pending_conn_sent_hvr(DTLS_LISTENER *dl)
+{
+    SSL *ssl = NULL;
+    SSL_CONNECTION *sc;
+
+    if (ossl_dgram_conn_lookup_num_items(dl->pending_conns) != 1)
+        return 0;
+    ossl_dgram_conn_lookup_foreach(dl->pending_conns, get_pending_conn_cb, &ssl);
+    sc = SSL_CONNECTION_FROM_SSL_ONLY(ssl);
+    return sc != NULL
+        && sc->statem.hand_state == DTLS_ST_SW_HELLO_VERIFY_REQUEST;
+}
+
+/*
+ * Regression test: a DTLS 1.2 pending connection which has sent an HVR and
+ * then fails on a fatal alert from its peer must be freed on that tick, not
+ * kept until the pending timeout.
+ */
+static int test_dtls12_listener_alert_after_hvr_frees_pending(void)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *listener = NULL, *clientssl = NULL;
+    BIO_ADDR *server_addr = NULL;
+    int server_fd = -1, client_fd = -1;
+    DTLS_LISTENER *dl;
+    int testresult = 0, k;
+    static const unsigned char alert[] = {
+        /* Record header */
+        SSL3_RT_ALERT, /* content type */
+        (DTLS1_VERSION >> 8) & 0xff, /* protocol version hi byte */
+        DTLS1_VERSION & 0xff, /* protocol version lo byte */
+        0, 0, /* epoch */
+        0, 0, 0, 0, 0, 1, /* record sequence */
+        0, 2, /* record length */
+
+        /* Alert */
+        SSL3_AL_FATAL, /* level */
+        SSL3_AD_HANDSHAKE_FAILURE /* description */
+    };
+
+    if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+            DTLS_client_method(), DTLS1_2_VERSION, DTLS1_2_VERSION,
+            &sctx, &cctx, cert, privkey)))
+        goto end;
+
+    if (!TEST_true(create_udp_listener(sctx, SSL_LISTENER_FLAG_SINGLE_THREAD,
+            &listener, &server_addr, &server_fd)))
+        goto end;
+    dl = (DTLS_LISTENER *)listener;
+
+    if (!TEST_true(create_dtls_client_for_addr(cctx, server_addr,
+            &clientssl, &client_fd))
+        || !client_step(clientssl))
+        goto end;
+
+    for (k = 0; k < 100 && !pending_conn_sent_hvr(dl); k++) {
+        listener_tick(listener);
+        OSSL_sleep(10);
+    }
+    if (!TEST_true(pending_conn_sent_hvr(dl)))
+        goto end;
+
+    if (!TEST_int_eq(BIO_write(SSL_get_wbio(clientssl), alert, sizeof(alert)),
+            (int)sizeof(alert)))
+        goto end;
+
+    for (k = 0; k < 100
+        && ossl_dgram_conn_lookup_num_items(dl->pending_conns) != 0;
+        k++) {
+        listener_tick(listener);
+        OSSL_sleep(10);
+    }
+    if (!TEST_size_t_eq(ossl_dgram_conn_lookup_num_items(dl->pending_conns),
+            0))
+        goto end;
+
+    testresult = 1;
+end:
+    SSL_free(clientssl);
+    if (client_fd >= 0)
+        BIO_closesocket(client_fd);
+    SSL_free(listener);
+    BIO_ADDR_free(server_addr);
+    if (server_fd >= 0)
+        BIO_closesocket(server_fd);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+}
+#endif /* OPENSSL_NO_DTLS1_2 */
 #endif /* !OPENSSL_NO_DTLS1_2 || !OPENSSL_NO_DTLS1_3 */

 #ifndef OPENSSL_NO_DTLS1_3
@@ -7775,6 +7873,9 @@ int setup_tests(void)
 #ifndef OPENSSL_NO_DTLS1_3
     ADD_TEST(test_dtls13_listener_conn_read_timeout);
 #endif
+#ifndef OPENSSL_NO_DTLS1_2
+    ADD_TEST(test_dtls12_listener_alert_after_hvr_frees_pending);
+#endif

     return 1;
 }