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: