From: Amaury Denoyelle Date: Fri, 7 Aug 2026 13:52:28 +0000 (+0200) Subject: BUG/MEDIUM: quic: prevent out-of-bound read on wrapping CRYPTO content X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b0f07463afc7edb6fb04b2b4f7438352ce04aa05;p=thirdparty%2Fhaproxy.git BUG/MEDIUM: quic: prevent out-of-bound read on wrapping CRYPTO content 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. --- diff --git a/src/quic_rx.c b/src/quic_rx.c index 49db1f10e..7cc1aaf14 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -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); } diff --git a/src/quic_ssl.c b/src/quic_ssl.c index 7961ce4cd..c329bffe9 100644 --- a/src/quic_ssl.c +++ b/src/quic_ssl.c @@ -507,6 +507,9 @@ static int ha_quic_ossl_crypto_recv_rcd(SSL *ssl, BUG_ON(ncbmb_is_null(ncbuf) || !cstream); /* 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))