]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: quic: implement the AEAD integrity limit
authorFrederic Lecaille <flecaille@haproxy.com>
Thu, 3 Sep 2026 13:10:47 +0000 (15:10 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Thu, 3 Sep 2026 17:44:36 +0000 (19:44 +0200)
RFC 9001 6.6 requires an endpoint to count the received packets which fail
authentication, and to close the connection with AEAD_LIMIT_REACHED once
their number exceeds the limit of the AEAD in use. This was not implemented.

Add quic_aead_integrity_limit() which returns that limit for each algorithm
tls_aead() may return, and count the failures in qc_pkt_decrypt(). This is the
only place where a packet is decrypted, whatever its encryption level, so the
count covers all the keys of the connection as the RFC asks. An unknown
algorithm gets a null limit, which closes the connection on the first failure:
QUIC must not use one.

With AES-GCM the limit is 2^52 failed packets, which at one million forged
packets per second on a single connection would take more than a century. With
ChaCha20-Poly1305 it is 2^36, about 19 hours at the same rate.

This is not a bug fix, nothing is affected in practice, so there is no hurry.
This may be backported to all the stable versions after some exposure in 3.5.

include/haproxy/quic_conn-t.h
include/haproxy/quic_tls.h
src/quic_conn.c
src/quic_rx.c

index c594fd835ac263333a78cf1be0b812d464571c4b..ab28093ee938fa1e0cab631b09d306c728f4454c 100644 (file)
@@ -386,6 +386,9 @@ struct quic_conn {
                /* first unhandled streams ID, set by MUX after release */
                uint64_t stream_max_uni;
                uint64_t stream_max_bidi;
+
+               /* Number of received packets which failed authentication. */
+               uint64_t auth_fails;
        } rx;
        struct {
                struct quic_tls_kp prv_rx;
index dac45a3f297a1baa91bf17f7a7e7000cc5db0849..e33437cca59c4026b7a29f6b666467f3dc8e2c63 100644 (file)
@@ -182,6 +182,44 @@ static inline const QUIC_AEAD *tls_aead(const SSL_CIPHER *cipher)
        }
 }
 
+/* Return the number of packets which may fail authentication with <aead> as
+ * AEAD algorithm, as stated by RFC 9001 6.6.
+ */
+static inline uint64_t quic_aead_integrity_limit(const QUIC_AEAD *aead)
+{
+       /* Note that QUIC_AEAD_API is defined only if OPENSSL_IS_AWSLC is defined.
+        *
+        * tls_aead() may only return, depending on the library:
+        *   AWS-LC        : aes_128_gcm, aes_256_gcm, chacha20_poly1305
+        *   wolfSSL       : aes_128_gcm, aes_256_gcm, chacha20_poly1305
+        *   LibreSSL < 4.0: aes_128_gcm, aes_256_gcm, aes_128_ccm
+        *   others        : the four of them
+        */
+#ifdef QUIC_AEAD_API
+       if (aead == EVP_aead_chacha20_poly1305())
+               return 1ULL << 36;
+       if (aead == EVP_aead_aes_128_gcm() ||
+           aead == EVP_aead_aes_256_gcm())
+               return 1ULL << 52;
+#else /* !QUIC_AEAD_API */
+       switch (EVP_CIPHER_nid(aead)) {
+#if !defined(USE_OPENSSL_WOLFSSL)
+       case NID_aes_128_ccm:
+               /* 2^21.5 */
+               return 2965820;
+#endif
+#if (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x4000000fL)
+       case NID_chacha20_poly1305:
+               return 1ULL << 36;
+#endif
+       case NID_aes_128_gcm:
+       case NID_aes_256_gcm:
+               return 1ULL << 52;
+       }
+#endif
+       return 0;
+}
+
 static inline const EVP_MD *tls_md(const SSL_CIPHER *cipher)
 {
        switch (SSL_CIPHER_get_id(cipher)) {
index 1f435a44dc3edb7006181590ee96ae99383fc780..a664b9fb1acb316dea373b101ffdb2700d90da60 100644 (file)
@@ -1307,6 +1307,7 @@ struct quic_conn *qc_new_conn(void *target,
        memset(&qc->rx.params, 0, sizeof(qc->rx.params));
        qc->rx.buf = b_make(qc->rx.buf.area, QUIC_CONN_RX_BUFSZ, 0, 0);
        qc->rx.stream_max_uni = qc->rx.stream_max_bidi = 0;
+       qc->rx.auth_fails = 0;
 
        qc->max_udp_payload = l ?
          l->bind_conf->quic_params.max_udp_payload_size :
index bff7f1d96a47e6adc916c11c96b80c652eab10f2..1bfe78fbb7fec9b16462d2b508b9d36c0971205f 100644 (file)
@@ -186,6 +186,21 @@ static int qc_pkt_decrypt(struct quic_conn *qc, struct quic_enc_level *qel,
                               rx_ctx, tls_ctx->rx.aead, rx_key, iv);
        if (!ret) {
                TRACE_ERROR("quic_tls_decrypt() failed", QUIC_EV_CONN_RXPKT, qc);
+
+               /* RFC 9001 6.6. Limits on AEAD Usage
+                *
+                * If the total number of received packets that fail
+                * authentication within the connection, across all keys,
+                * exceeds the integrity limit for the selected AEAD, the
+                * endpoint MUST immediately close the connection with a
+                * connection error of type AEAD_LIMIT_REACHED and not process
+                * any more packets.
+                */
+               if (++qc->rx.auth_fails > quic_aead_integrity_limit(tls_ctx->rx.aead)) {
+                       TRACE_ERROR("AEAD integrity limit reached", QUIC_EV_CONN_RXPKT, qc);
+                       quic_set_connection_close(qc, quic_err_transport(QC_ERR_AEAD_LIMIT_REACHED));
+               }
+
                goto leave;
        }