]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MAJOR: mux_quic: remove data not yet sent on reset stream
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 26 Oct 2026 10:21:32 +0000 (11:21 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 28 Oct 2026 10:06:00 +0000 (11:06 +0100)
When a stream is reset, it is expected that no more data will be
emitted. Thus, prepared data not yet sent is subtracted from the
connection flow control. However, nothing is performed for the data
itself, which may have important side effects.

Most of the time, this issue is invisible as RESET_STREAM frame is
finally emitted, QCS instance is fully closed and removed from the send
list, ready for its deletion. However, an issue is possible if the read
channel is not yet closed and output has been blocked for whatever
reason. If output is unblocked before the read channel closure, QCS may
emit new data STREAM frames, triggering a BUG_ON() on stream flow
control in qcm_ctrl_send(). This assertion reveals a possible violation
of RFC 9000 3.3 (STREAM frame sent in "Reset Sent" state) and of the
final size already advertised by RESET_STREAM frame.

The proper fix to this issue is to also clear prepared data when stream
is reset, along with the flow control subtraction. This guarantees that
no more STREAM frames will be emitted after stream reset, as any
potential snd_buf stream layer callback will drain content.

This must be backported to all stable releases. Note that qcs_tx_buf()
is available since 3.4, needs adjustment for previous releases.

Many thanks to "Sungho Park (@gigio1023)" for its report and analysis of
the issue.

src/mux_quic.c

index a7925ddb9fcc883eb66b9719c8cf2c82eb33e91e..bf538b3cb35007145e44a367507f0acb7b222d4d 100644 (file)
@@ -1815,12 +1815,20 @@ void qcc_reset_stream(struct qcs *qcs, int err, int tevt)
        if (conn_is_back(qcs->qcc->conn))
                qcs_idle_open(qcs);
 
+       /* Cancel emission of data not yet sent: clears not yet sent buffered
+        * data and subtract this amount from the connection flow control.
+        */
        if (diff) {
                const int soft_blocked = qfctl_sblocked(&qcc->tx.fc);
+               struct buffer *out = qcs_tx_buf(qcs);
 
                /* Soft offset cannot be inferior to real one. */
                BUG_ON(qcc->tx.fc.off_soft - diff < qcc->tx.fc.off_real);
 
+               /* Remove prepared data not yet sent. */
+               b_sub(out, diff);
+               qcs->flags &= ~QC_SF_FIN_STREAM;
+
                /* Subtract to conn flow control data amount prepared on stream not yet sent. */
                qcc->tx.fc.off_soft -= diff;
                if (soft_blocked && !qfctl_sblocked(&qcc->tx.fc))