Commit 6d6078160e for openssl.org

commit 6d6078160e43b6e79edf18eb68d6f9561a974ced
Author: Matt Caswell <matt@openssl.foundation>
Date:   Wed Aug 12 10:17:50 2026 +0100

    DTLS 1.3 Keep listener driven connections out of blocking mode

    A DTLS listener drives the handshakes of connections which have not been
    accepted yet from inside its own tick: dtls_listener_drive_pending()
    calls SSL_accept() on each of them. Those connections inherit the
    listener's blocking mode, so once a listener connection is able to block
    waiting for a datagram, that SSL_accept() can block too - and it must
    not, because the listener is the only thing which will ever deliver the
    datagram it is waiting for. Nothing else runs while the tick is inside
    that call, so the wait cannot end.

    Add force_nonblocking to DTLS1_STATE, set around the SSL_accept() that
    dtls_listener_drive_pending() performs, and consulted by
    ossl_dtls_desires_blocking(). Ownership of a connection passes to the
    application when SSL_accept_connection() returns it, and by then the
    flag is clear, so an application which asked for blocking still gets it.

    The flag has to survive SSL_clear(), which memsets d1. SSL_clear() can
    run inside the very SSL_accept() the listener is driving:
    drive_single_connection() sets TLS1_FLAGS_STATELESS to suppress it, but
    only where cookie validation is enabled. Losing the flag there is not
    merely a missed optimisation - the connection blocks inside the tick,
    poll_translate_ssl_dtls_conn() ticks the listener again from another
    thread, drive_pending re-enters and collects the same connection twice,
    and registering it a second time trips the old == NULL assertion in the
    connection lookup. In a release build that appears later as a
    use-after-free rather than as anything to do with blocking mode.

    Nothing blocks in a read yet, so this commit changes no behaviour on its
    own. It lands first so that the commit which adds blocking reads cannot
    deadlock at any point in the series.

    Assisted-by: Claude Code:claude-opus-5
    Reviewed-by: Ryan Hooper <ryanh@openssl.foundation>
    Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
    Merge-date: Mon Aug 17 08:29:56 2026
    Merged-from: https://github.com/openssl/openssl/pull/32324

diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index 5e6b558391..92ac3751e4 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -337,6 +337,8 @@ int dtls1_clear(SSL *ssl)
         SSL *listener = s->d1->listener;
         OSSL_TIME created_at = s->d1->created_at;
         unsigned int req_blocking_mode = s->d1->req_blocking_mode;
+        unsigned int force_nonblocking = s->d1->force_nonblocking;
+        unsigned int being_driven = s->d1->being_driven;
 #endif

         mtu = s->d1->mtu;
@@ -367,6 +369,19 @@ int dtls1_clear(SSL *ssl)
          * configured it, not of the handshake, so it survives a clear.
          */
         s->d1->req_blocking_mode = req_blocking_mode;
+        /*
+         * SSL_clear() can be called from inside the very SSL_accept() the
+         * listener is driving, so losing this would let the connection block
+         * there and stall the listener.
+         */
+        s->d1->force_nonblocking = force_nonblocking;
+        /*
+         * being_driven says the listener is driving this connection's
+         * handshake, and is what keeps a concurrent tick from collecting it a
+         * second time. Losing it would let two threads into the state machine
+         * for one connection.
+         */
+        s->d1->being_driven = being_driven;
         s->d1->created_at = created_at;
 #endif

@@ -2195,7 +2210,14 @@ static void drive_single_connection(SSL *ssl, DTLS_LISTENER *dl,
     if (dl->require_hrr_cookie || dl->require_hvr_cookie)
         sc->s3.flags |= TLS1_FLAGS_STATELESS;

+    /*
+     * We are inside the listener's own tick, so this must not block: nothing
+     * else can make progress while it does, including whatever it would be
+     * waiting for.
+     */
+    sc->d1->force_nonblocking = 1;
     ret = SSL_accept(ssl);
+    sc->d1->force_nonblocking = 0;

     /*
      * Always clear the stateless flag after SSL_accept() completes.
@@ -3003,6 +3025,10 @@ static int ossl_dtls_desires_blocking(const SSL *s)
     const DTLS_LISTENER *dl = NULL;

     if (sc != NULL && sc->d1 != NULL) {
+        /* The listener is driving this connection; it must not block. */
+        if (sc->d1->force_nonblocking)
+            return 0;
+
         if (sc->d1->req_blocking_mode != DTLS_BLOCKING_MODE_INHERIT)
             return sc->d1->req_blocking_mode == DTLS_BLOCKING_MODE_BLOCKING;

diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index bccfdc3786..3b47c6cdfb 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -656,6 +656,8 @@ int ossl_ssl_connection_reset(SSL *s)
         SSL *saved_listener = NULL;
         OSSL_TIME saved_created_at = ossl_time_zero();
         unsigned int saved_req_blocking_mode = DTLS_BLOCKING_MODE_INHERIT;
+        unsigned int saved_force_nonblocking = 0;
+        unsigned int saved_being_driven = 0;
         int is_dtls_listener_conn = 0;

         if (SSL_CONNECTION_IS_DTLS(sc) && sc->d1 != NULL
@@ -666,6 +668,8 @@ int ossl_ssl_connection_reset(SSL *s)
             saved_listener = sc->d1->listener;
             saved_created_at = sc->d1->created_at;
             saved_req_blocking_mode = sc->d1->req_blocking_mode;
+            saved_force_nonblocking = sc->d1->force_nonblocking;
+            saved_being_driven = sc->d1->being_driven;
             /*
              * Prevent dtls1_free from freeing rx and releasing the listener
              * reference - we'll restore them after ssl_init.
@@ -699,6 +703,14 @@ int ossl_ssl_connection_reset(SSL *s)
              * connection, not handshake state, so it survives a clear.
              */
             sc->d1->req_blocking_mode = saved_req_blocking_mode;
+            /*
+             * Both of these say something about the call this SSL_clear() may
+             * be nested inside: that the listener is driving the handshake and
+             * that it must not block while doing so. dtls1_clear() carries them
+             * over for the same reason.
+             */
+            sc->d1->force_nonblocking = saved_force_nonblocking;
+            sc->d1->being_driven = saved_being_driven;
         }
 #endif
     } else {
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index da04e6ddad..cc059ca647 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -2261,6 +2261,14 @@ typedef struct dtls1_state_st {
      * Defaults to inheriting from the listener it came from.
      */
     unsigned int req_blocking_mode : 2;
+
+    /*
+     * Set while the listener itself is driving this connection's handshake, to
+     * stop it blocking. The listener drives pending connections from inside its
+     * own tick, so a connection which blocked there would stop the listener
+     * making any further progress, including the progress being waited for.
+     */
+    unsigned int force_nonblocking : 1;
 #endif

 } DTLS1_STATE;
diff --git a/test/dtlsssllistenertest.c b/test/dtlsssllistenertest.c
index edbf8dbe71..b93dc268cc 100644
--- a/test/dtlsssllistenertest.c
+++ b/test/dtlsssllistenertest.c
@@ -28,6 +28,7 @@
 #include "internal/time.h"
 #include "internal/sockets.h"
 #include "internal/dgram_demux.h"
+#include "internal/ssl_unwrap.h"
 #include "helpers/ssltestlib.h"
 #include "testutil.h"
 #include "../ssl/ssl_local.h"
@@ -5411,6 +5412,7 @@ static int test_dtls_blocking_mode(void)
     SSL *listener = NULL, *clientssl = NULL, *serverssl = NULL;
     SSL *memlistener = NULL, *memclient = NULL, *plainssl = NULL;
     BIO_ADDR *server_addr = NULL, *client_addr = NULL;
+    SSL_CONNECTION *sc;
     int server_fd = -1, client_fd = -1;
     int testresult = 0;

@@ -5472,8 +5474,25 @@ static int test_dtls_blocking_mode(void)
         || !TEST_int_eq(SSL_get_blocking_mode(serverssl), 0))
         goto end;

+    /*
+     * being_driven has to survive a clear as well, for a different reason: it
+     * records that the listener is driving this connection's handshake, and is
+     * what stops a concurrent tick collecting the same connection a second
+     * time. The listener can reach SSL_clear() from inside the very
+     * SSL_accept() it is driving, so losing it there would admit a second
+     * thread to the state machine for this connection.
+     *
+     * It has to be set by hand, being held only for the duration of a call
+     * inside the listener's tick, which is not observable from out here.
+     */
+    if (!TEST_ptr(sc = SSL_CONNECTION_FROM_SSL_ONLY(serverssl)))
+        goto end;
+    sc->d1->being_driven = 1;
+
     if (!TEST_true(SSL_clear(serverssl))
-        || !TEST_int_eq(SSL_get_blocking_mode(serverssl), 0))
+        || !TEST_int_eq(SSL_get_blocking_mode(serverssl), 0)
+        || !TEST_ptr(sc = SSL_CONNECTION_FROM_SSL_ONLY(serverssl))
+        || !TEST_int_eq(sc->d1->being_driven, 1))
         goto end;

     /*
@@ -5483,9 +5502,13 @@ static int test_dtls_blocking_mode(void)
      * d1, so both have to be covered.
      */
     if (!TEST_true(SSL_clear(serverssl))
-        || !TEST_int_eq(SSL_get_blocking_mode(serverssl), 0))
+        || !TEST_int_eq(SSL_get_blocking_mode(serverssl), 0)
+        || !TEST_ptr(sc = SSL_CONNECTION_FROM_SSL_ONLY(serverssl))
+        || !TEST_int_eq(sc->d1->being_driven, 1))
         goto end;

+    sc->d1->being_driven = 0;
+
     /* And back the other way round. */
     if (!TEST_true(SSL_set_blocking_mode(listener, 1))
         || !TEST_true(SSL_set_blocking_mode(serverssl, 0))