Commit 9416706d40 for openssl.org
commit 9416706d408bb84deb7cee4647bff3045d2dc7ba
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date: Thu Jul 23 09:38:02 2026 +0200
QUIC server: limit number of pending QUIC channels/connections
Currently, there is no limit for pending QUIC connections. The port
default packet handler creates channel for every valid initial packet
which does belong to existing channel (a.k.a. connection). The newly
created channel is inserted to list of pending channels where it waits
to be accepted by local application by call
to SSL_accept_connection(3ossl).
This change introduces a limit for pending connection. The pending
queue is limited to 256 pending connections. Applications may change
the limit by calling SSL_set_feature_request_uint(3ossl)
on SSL server listener object with configurable value
SSL_VALUE_QUIC_MAX_PENDING_CONNS.
Fixes: 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:25 2026
(Merged from https://github.com/openssl/openssl/pull/32052)
diff --git a/doc/man3/SSL_get_value_uint.pod b/doc/man3/SSL_get_value_uint.pod
index c757f1ba25..f9eab38b53 100644
--- a/doc/man3/SSL_get_value_uint.pod
+++ b/doc/man3/SSL_get_value_uint.pod
@@ -15,6 +15,7 @@ SSL_VALUE_QUIC_STREAM_UNI_REMOTE_AVAIL, SSL_VALUE_QUIC_IDLE_TIMEOUT,
SSL_VALUE_QUIC_UDP_PAYLOAD_SIZE_MAX, SSL_VALUE_QUIC_WINDOWCON,
SSL_VALUE_QUIC_WINDOWBSTR, SSL_VALUE_QUIC_WINDOWUSTR,
SSL_VALUE_QUIC_ACK_DELAY_EXPONENT, SSL_VALUE_QUIC_ACK_DELAY_MAX,
+SSL_VALUE_QUIC_MAX_PENDING_CONNS,
SSL_VALUE_EVENT_HANDLING_MODE,
SSL_VALUE_EVENT_HANDLING_MODE_INHERIT,
SSL_VALUE_EVENT_HANDLING_MODE_EXPLICIT,
@@ -54,6 +55,7 @@ manage negotiable features and configuration values for an SSL object
#define SSL_VALUE_QUIC_WINDOWUSTR
#define SSL_VALUE_QUIC_ACK_DELAY_EXPONENT
#define SSL_VALUE_QUIC_ACK_DELAY_MAX
+ #define SSL_VALUE_QUIC_MAX_PENDING_CONNS
#define SSL_VALUE_EVENT_HANDLING_MODE
#define SSL_VALUE_EVENT_HANDLING_MODE_INHERIT
@@ -236,6 +238,16 @@ peer in the transport parameters.
This release of OpenSSL uses a default value of 25 milliseconds. This default
value may change between releases of OpenSSL.
+=item B<SSL_VALUE_QUIC_MAX_PENDING_CONNS> (listener object)
+
+Generic value, sets the limit on channels (connection objects) which a QUIC server can
+insert into the list of pending connections. A pending connection is a connection
+which the local application needs to accept (L<SSL_accept_connection(3)>) in order to retrieve
+an SSL connection object. The connection is removed from the pending queue by a call
+to L<SSL_accept_connection(3)>. The default limit for pending connections is 256. An INITIAL
+QUIC packet, which is received by a QUIC server with a full pending connections
+queue, is silently discarded. Setting the value to zero disables the limit.
+
=item B<SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL> (connection object)
Generic read-only statistical value. The number of bidirectional,
@@ -403,7 +415,8 @@ time.
L<SSL_ctrl(3)>, L<SSL_get_accept_stream_queue_len(3)>,
L<SSL_get_stream_read_state(3)>, L<SSL_get_stream_write_state(3)>,
L<SSL_get_stream_read_error_code(3)>, L<SSL_get_stream_write_error_code(3)>,
-L<SSL_set_default_stream_mode(3)>, L<SSL_set_incoming_stream_policy(3)>
+L<SSL_set_default_stream_mode(3)>, L<SSL_set_incoming_stream_policy(3)>,
+L<SSL_accept_connection(3)>
=head1 HISTORY
@@ -412,6 +425,9 @@ SSL_VALUE_QUIC_WINDOWBSTR, SSL_VALUE_QUIC_WINDOWUSTR,
SSL_VALUE_QUIC_ACK_DELAY_EXPONENT, and SSL_VALUE_QUIC_ACK_DELAY_MAX
were added in OpenSSL 4.1.
+The value SSL_VALUE_QUIC_MAX_PENDING_CONNS has been added in OpenSSL 4.1
+and ported to older releases 4.0.2, 3.6.4 and 3.5.8.
+
The remaining functions and values described here were all added in OpenSSL 3.3.
=head1 COPYRIGHT
diff --git a/include/internal/quic_port.h b/include/internal/quic_port.h
index 0ce54dea97..90f42e0e1c 100644
--- a/include/internal/quic_port.h
+++ b/include/internal/quic_port.h
@@ -241,6 +241,10 @@ uint64_t ossl_quic_port_get_net_bio_epoch(const QUIC_PORT *port);
void ossl_quic_port_raise_net_error(QUIC_PORT *port,
QUIC_CHANNEL *triggering_ch);
+uint64_t ossl_quic_port_get_max_pending_channels(const QUIC_PORT *port);
+
+void ossl_quic_port_set_max_pending_channels(QUIC_PORT *port, uint64_t max_pending_channels);
+
#endif
#endif
diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in
index b010a6e677..2215cc4176 100644
--- a/include/openssl/ssl.h.in
+++ b/include/openssl/ssl.h.in
@@ -2446,6 +2446,7 @@ __owur int SSL_get_conn_close_info(SSL *ssl,
#define SSL_VALUE_QUIC_WINDOWUSTR 13
#define SSL_VALUE_QUIC_ACK_DELAY_EXPONENT 14
#define SSL_VALUE_QUIC_ACK_DELAY_MAX 15
+#define SSL_VALUE_QUIC_MAX_PENDING_CONNS 16
#define SSL_VALUE_EVENT_HANDLING_MODE_INHERIT 0
#define SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT 1
diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c
index 3141912a61..dae03be3e2 100644
--- a/ssl/quic/quic_impl.c
+++ b/ssl/quic/quic_impl.c
@@ -3999,6 +3999,33 @@ err:
return ret;
}
+QUIC_TAKES_LOCK
+static int qc_getset_max_pending_channels(QCTX *ctx, uint32_t class_,
+ uint64_t *p_value_out, uint64_t *p_value_in)
+{
+ int ret = 0;
+ uint64_t value_out = 0;
+
+ qctx_lock(ctx);
+
+ if (class_ == SSL_VALUE_CLASS_GENERIC && ctx->is_listener) {
+ value_out = ossl_quic_port_get_max_pending_channels(ctx->ql->port);
+ if (p_value_in != NULL)
+ ossl_quic_port_set_max_pending_channels(ctx->ql->port, *p_value_in);
+ ret = 1;
+ } else {
+ QUIC_RAISE_NON_NORMAL_ERROR(ctx, SSL_R_UNSUPPORTED_CONFIG_VALUE_CLASS, NULL);
+ ret = 0;
+ }
+
+ qctx_unlock(ctx);
+
+ if (ret && p_value_out != NULL)
+ *p_value_out = value_out;
+
+ return ret;
+}
+
QUIC_TAKES_LOCK
static int qc_get_stream_avail(QCTX *ctx, uint32_t class_,
int is_uni, int is_remote,
@@ -4141,6 +4168,7 @@ static int expect_quic_for_value(SSL *s, QCTX *ctx, uint32_t id)
case SSL_VALUE_QUIC_WINDOWUSTR:
case SSL_VALUE_QUIC_ACK_DELAY_EXPONENT:
case SSL_VALUE_QUIC_ACK_DELAY_MAX:
+ case SSL_VALUE_QUIC_MAX_PENDING_CONNS:
return expect_quic_cl(s, ctx);
default:
return expect_quic_conn_only(s, ctx);
@@ -4175,6 +4203,8 @@ int ossl_quic_get_value_uint(SSL *s, uint32_t class_, uint32_t id,
return qc_getset_ack_delay_exponent(&ctx, class_, value, NULL);
case SSL_VALUE_QUIC_ACK_DELAY_MAX:
return qc_getset_max_ack_delay(&ctx, class_, value, NULL);
+ case SSL_VALUE_QUIC_MAX_PENDING_CONNS:
+ return qc_getset_max_pending_channels(&ctx, class_, value, NULL);
case SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL:
return qc_get_stream_avail(&ctx, class_, /*uni=*/0, /*remote=*/0, value);
@@ -4233,6 +4263,8 @@ int ossl_quic_set_value_uint(SSL *s, uint32_t class_, uint32_t id,
return qc_getset_ack_delay_exponent(&ctx, class_, NULL, &value);
case SSL_VALUE_QUIC_ACK_DELAY_MAX:
return qc_getset_max_ack_delay(&ctx, class_, NULL, &value);
+ case SSL_VALUE_QUIC_MAX_PENDING_CONNS:
+ return qc_getset_max_pending_channels(&ctx, class_, NULL, &value);
default:
return QUIC_RAISE_NON_NORMAL_ERROR(&ctx,
diff --git a/ssl/quic/quic_port.c b/ssl/quic/quic_port.c
index 9115143f52..afd21fec2a 100644
--- a/ssl/quic/quic_port.c
+++ b/ssl/quic/quic_port.c
@@ -101,6 +101,8 @@ typedef struct validation_token {
#define DEFAULT_INIT_CONN_MAX_STREAMS 100
+#define DEFAULT_MAX_PENDING_CONNS 256
+
DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
DEFINE_LIST_OF_IMPL(incoming_ch, QUIC_CHANNEL);
DEFINE_LIST_OF_IMPL(port, QUIC_PORT);
@@ -118,6 +120,7 @@ QUIC_PORT *ossl_quic_port_new(const QUIC_PORT_ARGS *args)
port->validate_addr = args->do_addr_validation;
port->get_conn_user_ssl = args->get_conn_user_ssl;
port->ql = args->ql;
+ port->max_pending_channels = DEFAULT_MAX_PENDING_CONNS;
if (!port_init(port)) {
OPENSSL_free(port);
@@ -1708,6 +1711,9 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
if (hdr.type != QUIC_PKT_TYPE_INITIAL)
goto undesirable;
+ if (port->max_pending_channels > 0 && ossl_list_incoming_ch_num(&port->incoming_channel_list) >= port->max_pending_channels)
+ goto undesirable;
+
odcid.id_len = 0;
/*
@@ -1991,3 +1997,13 @@ uint64_t ossl_quic_port_get_active_conn_id_limit(const QUIC_PORT *port)
{
return port->active_conn_id_limit;
}
+
+uint64_t ossl_quic_port_get_max_pending_channels(const QUIC_PORT *port)
+{
+ return port->max_pending_channels;
+}
+
+void ossl_quic_port_set_max_pending_channels(QUIC_PORT *port, uint64_t max_pending_channels)
+{
+ port->max_pending_channels = max_pending_channels;
+}
diff --git a/ssl/quic/quic_port_local.h b/ssl/quic/quic_port_local.h
index c096210863..8f7ccf5318 100644
--- a/ssl/quic/quic_port_local.h
+++ b/ssl/quic/quic_port_local.h
@@ -135,6 +135,7 @@ struct quic_port_st {
uint64_t active_conn_id_limit;
unsigned char ack_delay_exponent;
unsigned char disable_active_migration;
+ uint64_t max_pending_channels;
};
#endif
diff --git a/util/other.syms b/util/other.syms
index 114e25a348..c0a2358b54 100644
--- a/util/other.syms
+++ b/util/other.syms
@@ -807,6 +807,7 @@ SSL_VALUE_QUIC_WINDOWBSTR define
SSL_VALUE_QUIC_WINDOWUSTR define
SSL_VALUE_QUIC_ACK_DELAY_EXPONENT define
SSL_VALUE_QUIC_ACK_DELAY_MAX define
+SSL_VALUE_QUIC_MAX_PENDING_CONNS define
SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL define
SSL_VALUE_QUIC_STREAM_BIDI_REMOTE_AVAIL define
SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL define