]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MAJOR: mux_quic: fix leak on RESET_STREAM reception
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 23 Sep 2026 12:32:30 +0000 (14:32 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 24 Sep 2026 09:17:31 +0000 (11:17 +0200)
On QUIC MUX, <nb_hreq> counter serves to account for the currently
active requests in progress. While this number is non null, the
connection is considered active and cannot be released. The counter is
decremented only when the QCS instance is finally freed. This guarantees
that no truncated request could happen.

An issue happened on RESET_STREAM frame reception as the first frame for
a new QCS instance. In this case, <nb_hreq> is incremented. No progress
will occur as the upper stream layer is not instantiated, and QCS
instance will remains half-closed, which prevents its liberation.

The first issue is that it causes a leak of QCS during the connection
lifetime. Worst, this may prevent the whole connection closure even if
idle timeout as fired : this is because qcc_is_dead() checks <nb_hreq>
first before regarding transport layer status. Note that this only
happens if QUIC MUX timeout is not yet activated, which is the case if
qcc_io_cb() has not yet been executed.

This patch fixes this situation by improving RESET_STREAM receive
handler. Now, an extra check is performed to detect if the frame has
been received before the upper stream layer init. If true, QCS instance
is closed locally and immediately scheduled for purge, which ensures
that <nb_hreq> counter will be decremented as expected.

This must be backported up to 2.8.

src/mux_quic.c

index a8a552cb6ae0f14819eb704288be5b102bc3c713..b5da163cffa1d756e2ebd3a3003cae645c5479b1 100644 (file)
@@ -2360,6 +2360,18 @@ int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t f
        /* Remove stream from recv_list if present. */
        LIST_DEL_INIT(&qcs->el_recv);
 
+       /* Check if RESET_STREAM received before stream layer initialization.
+        * If true, prepare QCS purgeing immediately.
+        */
+       if (!qcs_sc(qcs) && !(qcs->flags & QC_SF_DETACH)) {
+               qcs_close_local(qcs);
+
+               BUG_ON(LIST_INLIST(&qcs->el_send));
+               TRACE_STATE("add stream in purg_list", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
+               LIST_APPEND(&qcs->qcc->purg_list, &qcs->el_send);
+               tasklet_wakeup(qcs->qcc->wait_event.tasklet);
+       }
+
  out:
        if (qcc->glitches != prev_glitches && !(qcc->flags & QC_CF_IS_BACK))
                session_add_glitch_ctr(qcc->conn->owner, qcc->glitches - prev_glitches);