]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: quic: prevent out-of-bound read on wrapping CRYPTO content
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 7 Aug 2026 13:52:28 +0000 (15:52 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 7 Aug 2026 14:25:50 +0000 (16:25 +0200)
Received CRYPTO frames are buffered in a ncbmbuf to handle out-of-order
reception. When new content is available at the current offset, TLS
stack is notified so that it can read it. This is performed either via
ha_quic_ossl_crypto_recv_rcd() (for OpenSSL 3.5+) or
qc_ssl_provide_all_quic_data().

Depending on the receiving order, CRYPTO content may wrap over time.
This is currently not supported by haproxy as stated in a comment in
qc_ssl_provide_all_quic_data(), however there is no explicit code
protection to avoid it. Thus, a TLS stack may read past the CRYPTO
content as ncbmb_data() will report the size of data with wrapping
included.

The objective of this patch is to prevent any out-of-bound read attempt
by closing the connection on error. A check is added after buffering a
new CRYPTO frame in qc_handle_crypto_frm() : if content is wrapping, an
error CRYPTO_BUFFER_EXCEEDED is reported, the buffer is released and the
connection is closed. This happens before ha_quic_ossl_crypto_recv_rcd()
or qc_ssl_provide_all_quic_data() so this is a sufficient fix.

Note that the first idea was to directly patch
ha_quic_ossl_crypto_recv_rcd() / qc_ssl_provide_all_quic_data(). However
it is not easy as return value of these functions is ignored by their
callers.

Currently, this bug is unlikely as it's not possible to obtain a
condition for CRYPTO content to wrap with the available combination of
QUIC clients and their SSL library. It was reproduced only after manual
modification on ncbmb_init() to setup head buffer pointer near its end.
However, it's not guaranteed to not occur even without code patching so
the current fix is still necessary.

In the future, it may be necessary to complete this patch so that CRYPTO
wrapping can be realigned and decoded. A COUNT_IF() has been added to
help detect when this is the case.

Reported-by: Claude (ANT-2026-Y2QP9HED)
This should be backported up to 2.6.

src/quic_rx.c
src/quic_ssl.c

index 49db1f10ec2f6ac009335d846929aee43d24e410..7cc1aaf14aae6e1ff972f762f8de48e193e98569 100644 (file)
@@ -669,6 +669,7 @@ static int qc_handle_crypto_frm(struct quic_conn *qc,
        struct quic_cstream *cstream = qel->cstream;
        struct ncbmbuf *ncbuf = &qel->cstream->rx.ncbuf;
        uint64_t off_rel;
+       ncb_sz_t data;
 
        TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc);
 
@@ -718,8 +719,22 @@ static int qc_handle_crypto_frm(struct quic_conn *qc,
                            crypto_frm->len, NCB_ADD_OVERWRT);
        BUG_ON(ncb_ret != NCB_RET_OK);
 
+       data = ncbmb_data(ncbuf, 0);
        /* Reschedule with TASK_HEAVY if CRYPTO data ready for decoding. */
-       if (ncbmb_data(ncbuf, 0)) {
+       if (data) {
+               /* Reject CRYPTO content in case of wrapping. This ensures
+                * there is no read of out-of-bound read by the SSL stack in
+                * ha_quic_ossl_crypto_recv_rcd()/qc_ssl_provide_all_quic_data().
+                * TODO implement proper support for CRYPTO wrapping.
+                */
+               if (ncbmb_head(ncbuf) + data >= ncbmb_wrap(ncbuf)) {
+                       TRACE_ERROR("unsupported wrapping CRYPTO frames", QUIC_EV_CONN_PRSHPKT, qc);
+                       COUNT_IF(1, "connection closed on unsupported wrapping CRYPTO content");
+                       quic_set_connection_close(qc, quic_err_transport(QC_ERR_CRYPTO_BUFFER_EXCEEDED));
+                       quic_free_ncbuf(ncbuf);
+                       goto err;
+               }
+
                HA_ATOMIC_OR(&qc->wait_event.tasklet->state, TASK_HEAVY);
                tasklet_wakeup(qc->wait_event.tasklet);
        }
index 7961ce4cd8d7e19374f46de95b3f83a00619443e..c329bffe91e12f823aa322313ea1cd5ee2eb7a4f 100644 (file)
@@ -507,6 +507,9 @@ static int ha_quic_ossl_crypto_recv_rcd(SSL *ssl,
                BUG_ON(ncbmb_is_null(ncbuf) || !cstream);
                /* <ncbuf> must not be released at this time. */
                cdata = (const unsigned char *)ncbmb_head(ncbuf);
+               /* Currently wrapping CRYPTO content is not supported and rejected on frame reception. */
+               BUG_ON(cdata + data >= (const unsigned char *)ncbmb_wrap(ncbuf));
+
                cstream->rx.offset += data;
                TRACE_DEVEL("buffered crypto data were provided to TLS stack",
                                        QUIC_EV_CONN_PHPKTS, qc, qel);
@@ -1187,6 +1190,8 @@ int qc_ssl_provide_all_quic_data(struct quic_conn *qc, struct ssl_sock_ctx *ctx)
                /* TODO not working if buffer is wrapping */
                while ((data = ncbmb_data(ncbuf, 0))) {
                        const unsigned char *cdata = (const unsigned char *)ncbmb_head(ncbuf);
+                       /* Currently wrapping CRYPTO content is not supported and rejected on frame reception. */
+                       BUG_ON(cdata + data >= (const unsigned char *)ncbmb_wrap(ncbuf));
 
                        if (!qc_ssl_provide_quic_data(&qel->cstream->rx.ncbuf, qel->level,
                                                      ctx, cdata, data))