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

Reply via email to