From: Willy Tarreau Date: Mon, 9 Nov 2026 19:14:45 +0000 (+0100) Subject: BUG/MINOR: mux-h2: also emit the HPACK Dynamic Table Size Update before trailers X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=c5e93986efab35b44989c02446b08309a20a6d86;p=haproxy.git BUG/MINOR: mux-h2: also emit the HPACK Dynamic Table Size Update before trailers Commit 39a0a1e12 ("MEDIUM: h2/hpack: emit a Dynamic Table Size Update after settings change") made the mux emit an HPACK Dynamic Table Size Update at the beginning of the first header block following a change of the peer's SETTINGS_HEADER_TABLE_SIZE (RFC7541#4.2). The previous commit did the same for request HEADERS, but h2s_make_trailers() still never emits it, on any side, so that if the first header block following the change is a trailers block, a strict decoder will report a COMPRESSION_ERROR and kill the connection. It's quite unlikely to happen to be honest, because: - on the frontend, the client sends the SETTINGS frame before headers and has no real reason to change it afterwards ; - on the backend, it could theoretically happen but only on a large POST that receives a SETTINGS frame after the first HEADERS frame and acks it before the trailers HEADERS frame; it's not as if trailers to backends were that common anyway. The update is now inserted before the encoded fields of the trailers when at least one field is present. Otherwise an empty DATA frame is sent instead of a HEADERS frame, and the update is left for the next header block. The flags are only updated once the frame is committed, as in the other functions. This was verified on the backend with a server sending its SETTINGS frame with a lower header table size after receiving a request's HEADERS frame: the request's trailers now start with the update, which was not the case before. The frontend case was only verified by code analysis, it uses the same code. Like previous patch, this could be backported to all stable versions, but preferably only if found to be necessary. Let's just start with 3.3 and 3.4 only. --- diff --git a/src/mux_h2.c b/src/mux_h2.c index 762d53f65..ee5ce7a90 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -8020,6 +8020,23 @@ static size_t h2s_make_trailers(struct h2s *h2s, struct htx *htx) } } + if (outbuf.data > 9 && + (h2c->flags & (H2_CF_SHTS_UPDATED|H2_CF_DTSU_EMITTED)) == H2_CF_SHTS_UPDATED) { + /* SETTINGS_HEADER_TABLE_SIZE changed, we must send an HPACK + * dynamic table size update at the beginning of the next + * header block (see h2s_snd_fhdrs()). It's inserted before the + * encoded fields. + */ + if (b_room(&outbuf) < 1) { + if (b_space_wraps(mbuf)) + goto realign_again; + goto full; + } + memmove(outbuf.area + 10, outbuf.area + 9, outbuf.data - 9); + outbuf.area[9] = 0x20; // HPACK DTSU 0 bytes + outbuf.data++; + } + if (outbuf.data == 9) { /* here we have a problem, we have nothing to emit (either we * received an empty trailers block followed or we removed its @@ -8049,6 +8066,12 @@ static size_t h2s_make_trailers(struct h2s *h2s, struct htx *htx) h2c->flags |= H2_CF_MBUF_HAS_DATA; h2s->flags |= H2_SF_ES_SENT; + if (outbuf.data > 9 && (h2c->flags & H2_CF_SHTS_UPDATED)) { + /* was sent above (only in a HEADERS frame) */ + h2c->flags |= H2_CF_DTSU_EMITTED; + h2c->flags &= ~H2_CF_SHTS_UPDATED; + } + if (h2s->st == H2_SS_OPEN) h2s->st = H2_SS_HLOC; else