Hi, Sending again because the patch was scrambled.
After upgrading to 3.4.1 we hit repeated BUG_ON crashes in __b_putblk() triggered by the FCGI drain fix (84b952515). The drain fix is correct, but it exposed missing b_room() checks in four b_xfer() zero-copy call sites. Patch in attachment fixes all four. --yas
From 5a5100252ad21f17a7f437fc1828541bd5784218 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama <[email protected]> Date: Sun, 28 Jun 2026 18:13:47 +0300 Subject: [PATCH] BUG/MEDIUM: fix b_xfer overflow in zero-copy paths triggered by mux drain fixes Commit 84b952515 ("BUG/MEDIUM: mux-fcgi: Truly drain outgoing HTX data when the stream is closed") correctly drains the FCGI output buffer on stream close. Before that fix, incomplete draining kept the buffer artificially full, hiding a pre-existing bug in several b_xfer() zero-copy call sites. After the drain fix, b_room() returns a larger value, and the following b_xfer() calls proceed with b_data(src) as the count without verifying that the destination has sufficient room: - appctx_htx_rcv_buf() checks b_size() equality but not b_room(), so after htx_to_buf() the raw byte count may not fit. - h1_nego_ff() used b_contig_space() which only accounts for contiguous tail room, not total room when source data wraps. - h2_rcv_buf() and qcs_http_rcv_buf() have neither check. The overflow triggers a BUG_ON in __b_putblk() on the second (wrapped-data) block: FATAL: bug condition "b_data(b) + len > b_size(b)" matched at src/buf.c:318 Add a b_room() check before each b_xfer() call in these zero-copy paths. If the destination lacks room, ret is set to 0 so the caller falls back to the regular htx_xfer() path. This must be backported to 3.4. --- src/applet.c | 5 ++++- src/mux_h1.c | 18 +++++++++++------- src/mux_h2.c | 5 ++++- src/qcm_http.c | 5 ++++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/applet.c b/src/applet.c index 6e5188404..9adba80ea 100644 --- a/src/applet.c +++ b/src/applet.c @@ -507,7 +507,10 @@ size_t appctx_htx_rcv_buf(struct appctx *appctx, struct buffer *buf, size_t coun if (b_size(&appctx->outbuf) == b_size(buf) && htx_is_empty(buf_htx) && htx_used_space(appctx_htx) <= count) { htx_to_buf(buf_htx, buf); htx_to_buf(appctx_htx, &appctx->outbuf); - b_xfer(buf, &appctx->outbuf, b_data(&appctx->outbuf)); + if (b_data(&appctx->outbuf) <= b_room(buf)) + b_xfer(buf, &appctx->outbuf, b_data(&appctx->outbuf)); + else + ret = 0; goto out; } diff --git a/src/mux_h1.c b/src/mux_h1.c index 41aa3c984..ef5f5d548 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -4975,7 +4975,7 @@ static size_t h1_nego_ff(struct stconn *sc, struct buffer *input, size_t count, struct h1s *h1s = __sc_mux_strm(sc); struct h1c *h1c = h1s->h1c; struct h1m *h1m = (!(h1c->flags & H1C_F_IS_BACK) ? &h1s->res : &h1s->req); - size_t sz, offset = 0, ret = 0; + size_t offset = 0, ret = 0, room = 0; TRACE_ENTER(H1_EV_STRM_SEND, h1c->conn, h1s, 0, (size_t[]){count}); @@ -5093,17 +5093,21 @@ static size_t h1_nego_ff(struct stconn *sc, struct buffer *input, size_t count, if (b_space_wraps(&h1c->obuf)) b_slow_realign(&h1c->obuf, trash.area, b_data(&h1c->obuf)); - if (b_contig_space(&h1c->obuf) <= offset) { + /* Cannot forward more than available room in output buffer. + * Use b_room() instead of b_contig_space() because the source + * data may be discontiguous: b_xfer() may split it in two and + * the second block needs room at the start of the buffer, not + * just at the tail as b_contig_space() provides. + */ + room = b_room(&h1c->obuf); + if (room <= offset) { h1c->flags |= H1C_F_OUT_FULL; h1s->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED; TRACE_STATE("output buffer full", H1_EV_STRM_SEND|H1_EV_H1S_BLK, h1c->conn, h1s); goto out; } - - /* Cannot forward more than available room in output buffer */ - sz = b_contig_space(&h1c->obuf) - offset; - if (count > sz) - count = sz; + if (count > room - offset) + count = room - offset; h1s->sd->iobuf.buf = &h1c->obuf; h1s->sd->iobuf.offset = offset; diff --git a/src/mux_h2.c b/src/mux_h2.c index 50656eb44..e503916e7 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -7976,7 +7976,10 @@ static size_t h2_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, in count -= h2s_htx->data; htx_to_buf(buf_htx, buf); htx_to_buf(h2s_htx, rxbuf); - b_xfer(buf, rxbuf, b_data(rxbuf)); + if (b_data(rxbuf) <= b_room(buf)) + b_xfer(buf, rxbuf, b_data(rxbuf)); + else + ret = 0; goto end; } diff --git a/src/qcm_http.c b/src/qcm_http.c index c2f0ae1eb..e31c3af4f 100644 --- a/src/qcm_http.c +++ b/src/qcm_http.c @@ -40,7 +40,10 @@ size_t qcs_http_rcv_buf(struct qcs *qcs, struct buffer *buf, size_t count, htx_to_buf(cs_htx, buf); htx_to_buf(qcs_htx, &qcs->rx.app_buf); - b_xfer(buf, &qcs->rx.app_buf, b_data(&qcs->rx.app_buf)); + if (b_data(&qcs->rx.app_buf) <= b_room(buf)) + b_xfer(buf, &qcs->rx.app_buf, b_data(&qcs->rx.app_buf)); + else + ret = 0; goto end; } -- 2.53.0

