Commit 0461a5636d for openssl.org

commit 0461a5636deca9af034db70ff017ad1138836c8c
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date:   Thu Jul 23 09:51:41 2026 +0200

    test/quicapitest.c: add test for pending connections limit enforcement

    This is a regression test for CVE-2026-14456.

    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Andrew Dinh <andrewd@openssl.org>
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    MergeDate: Wed Aug 12 15:00:30 2026
    (Merged from https://github.com/openssl/openssl/pull/32052)

diff --git a/include/internal/quic_ssl.h b/include/internal/quic_ssl.h
index 45b8e090ed..1140806a80 100644
--- a/include/internal/quic_ssl.h
+++ b/include/internal/quic_ssl.h
@@ -179,6 +179,7 @@ int ossl_quic_conn_poll_events(SSL *ssl, uint64_t events, int do_tick,
 int ossl_quic_get_notifier_fd(SSL *ssl);
 void ossl_quic_enter_blocking_section(SSL *ssl, QUIC_REACTOR_WAIT_CTX *wctx);
 void ossl_quic_leave_blocking_section(SSL *ssl, QUIC_REACTOR_WAIT_CTX *wctx);
+QUIC_PORT *ossl_quic_listener_get_port(SSL *s);

 #endif

diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c
index dae03be3e2..e80790aa25 100644
--- a/ssl/quic/quic_impl.c
+++ b/ssl/quic/quic_impl.c
@@ -5956,6 +5956,19 @@ QUIC_CHANNEL *ossl_quic_conn_get_channel(SSL *s)
     return ctx.qc->ch;
 }

+QUIC_PORT *ossl_quic_listener_get_port(SSL *s)
+{
+    QCTX ctx;
+
+    /*
+     * expect listerner only
+     */
+    if (!expect_quic_listener(s, &ctx))
+        return NULL;
+
+    return ctx.ql->port;
+}
+
 int ossl_quic_set_diag_title(SSL_CTX *ctx, const char *title)
 {
 #ifndef OPENSSL_NO_QLOG
diff --git a/test/quicapitest.c b/test/quicapitest.c
index a8de8f9670..72464d3344 100644
--- a/test/quicapitest.c
+++ b/test/quicapitest.c
@@ -22,6 +22,7 @@
 #include "../ssl/quic/quic_channel_local.h"
 #include "internal/quic_error.h"
 #include "internal/quic_ssl.h"
+#include "internal/quic_port.h"

 static OSSL_LIB_CTX *libctx = NULL;
 static char *propq = NULL;
@@ -3840,6 +3841,129 @@ err:
     return ret;
 }

+#define PENDING_LIMIT 5
+#define HANDSHAKE_STEPS 10
+static int test_pending_limit(void)
+{
+    SSL_CTX *cctx = NULL, *sctx = NULL;
+    SSL *clientssl = NULL, *serverssl_listener = NULL, *serverssl = NULL;
+    SSL *extra_clients[PENDING_LIMIT * 2] = { NULL };
+    BIO *bio;
+    unsigned int i, handshake_step;
+    int done;
+    int testresult = 0;
+    int ok;
+    QUIC_PORT *port;
+    size_t pending_connections = 0;
+
+    if (!TEST_true(create_quic_ctx_pair(libctx, &cctx, &sctx, cert, privkey)))
+        return 0;
+
+    if (!TEST_true(create_quic_conn_objects(cctx, sctx, &clientssl, &serverssl_listener)))
+        goto end;
+
+    ok = SSL_set_generic_value_uint(serverssl_listener,
+        SSL_VALUE_QUIC_MAX_PENDING_CONNS, PENDING_LIMIT);
+    if (!TEST_true(ok)) {
+        TEST_info("%s call to SSL_set_generic_request_uint"
+                  "(SSL_VALUE_QUIC_MAX_PENDING_CONNS failed",
+            __func__);
+        goto end;
+    }
+
+    if (!TEST_true(SSL_listen(serverssl_listener))) {
+        TEST_info("%s SSL_listen() failed", __func__);
+        goto end;
+    }
+
+    port = ossl_quic_listener_get_port(serverssl_listener);
+    if (!TEST_ptr(port))
+        goto end;
+
+    bio = SSL_get_rbio(clientssl);
+    if (!TEST_ptr(bio))
+        goto end;
+
+    if (!TEST_ptr_eq(bio, SSL_get_wbio(clientssl)))
+        goto end;
+
+    for (i = 0; i < OSSL_NELEM(extra_clients); i++) {
+        extra_clients[i] = create_quic_client(cctx, bio);
+        if (!TEST_ptr(extra_clients[i]))
+            goto end;
+    }
+
+    for (i = 0; i < PENDING_LIMIT; i++) {
+        handshake_step = 0;
+        done = 0;
+        while (!done && handshake_step++ < HANDSHAKE_STEPS) {
+            /*
+             * connections are never accepted by the server. The SSL_connect()
+             * for non-blocking client returns -1 to keep connect retrying
+             */
+            if (!TEST_int_lt(SSL_connect(extra_clients[i]), 0))
+                goto end;
+            SSL_handle_events(serverssl_listener);
+            pending_connections = ossl_quic_port_get_num_incoming_channels(port);
+            done = (pending_connections == (i + 1));
+        }
+    }
+
+    if (!TEST_size_t_eq(pending_connections, PENDING_LIMIT))
+        goto end;
+
+    /*
+     * initiate yet another connection. The connection must not be inserted
+     * to pending queue. The pending_connections must be 5.
+     */
+    for (i = PENDING_LIMIT; i < OSSL_NELEM(extra_clients); i++) {
+        handshake_step = 0;
+        done = 0;
+        while (!done && handshake_step++ < HANDSHAKE_STEPS) {
+            /*
+             * connections are never accepted by the server. The SSL_connect()
+             * for non-blocking client returns -1 to keep connect retrying
+             */
+            if (!TEST_int_le(SSL_connect(extra_clients[i]), 0))
+                goto end;
+            SSL_handle_events(serverssl_listener);
+            pending_connections = ossl_quic_port_get_num_incoming_channels(port);
+            done = (pending_connections == (i + 1));
+        }
+    }
+    pending_connections = ossl_quic_port_get_num_incoming_channels(port);
+    if (!TEST_size_t_eq(pending_connections, PENDING_LIMIT))
+        goto end;
+
+    /*
+     * accept one connection and check the length of the queue dropped to 4.
+     */
+    done = 0;
+    handshake_step = 0;
+    while (!done && handshake_step++ < HANDSHAKE_STEPS) {
+        if (!TEST_int_lt(SSL_connect(extra_clients[0]), 0))
+            goto end;
+        SSL_handle_events(serverssl_listener);
+        serverssl = SSL_accept_connection(serverssl_listener, 0);
+        done = (serverssl != NULL);
+    }
+    pending_connections = ossl_quic_port_get_num_incoming_channels(port);
+    if (!TEST_size_t_eq(pending_connections, PENDING_LIMIT - 1))
+        goto end;
+
+    testresult = 1;
+end:
+    for (i = 0; i < OSSL_NELEM(extra_clients); i++)
+        SSL_free(extra_clients[i]);
+    SSL_free(clientssl);
+    SSL_free(serverssl);
+    SSL_free(serverssl_listener);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+
+    return testresult;
+}
+
 /***********************************************************************************/
 OPT_TEST_DECLARE_USAGE("provider config certsdir datadir\n")

@@ -3955,6 +4079,7 @@ int setup_tests(void)
     ADD_TEST(test_ech);
     ADD_TEST(test_quic_resize_txe);
     ADD_MFAIL_TEST(test_ssl_new_mfail);
+    ADD_TEST(test_pending_limit);

     return 1;
 err: