From bece70412810b47e5eb98eda059252bf535b2992 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 21 Oct 2025 15:06:39 +0200 Subject: [PATCH] BUG/MEDIUM: h3: properly encode response after interim one in same buf Recently, proper support for interim responses forwarding to HTTP/3 client has been implemented. However, there was still an issue if two responses are both encoded in the same snd_buf() iteration. The issue is caused due to H3 HEADERS frame encoding method : 5 bytes are reserved in front of the buffer to encode both H3 frame type and varint length field. After proper headers encoding, output buffer head is adjusted so that length can be encoded using the minimal varint size. However, if the buffer is not empty due to a previous response already encoded but not yet emitted, messing with the buffer head will corrupt the entire H3 message. This only happens when encoding of both responses is done in the same snd_buf() iteration, or at least without emission to quic_conn layer in between. The result of this bug is that the HTTP/3 client will be unable to parse the response, most of the time reporting a formatting error. This can be reproduced using the following netcat as HTTP/1 server to haproxy : $ while sleep 0.2; do \ printf "HTTP/1.1 100 continue\r\n\r\nHTTP/1.1 200 ok\r\nContent-length: 5\r\nConnection: close\r\n\r\nblah\n" | nc -lp8002 done To fix this, only adjust buffer head if content is empty. If this is not the case, frame length is simply encoded as a 4-bytes varint size so that messages are contiguous in the buffer. This must be backported up to 2.6. --- src/h3.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/h3.c b/src/h3.c index 5b94ea40a..4407bca94 100644 --- a/src/h3.c +++ b/src/h3.c @@ -2363,10 +2363,23 @@ static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx) /* Now that all headers are encoded, we are certain that res buffer is * big enough */ - frame_length_size = quic_int_getsize(b_data(&headers_buf)); - res->head += 4 - frame_length_size; - b_putchr(res, 0x01); /* h3 HEADERS frame type */ - b_quic_enc_int(res, b_data(&headers_buf), 0); + + /* Encode H3 HEADERS frame type + frame length. If buffer is empty, + * length is encoded on min varint size and buf head is realigned. + * However, if it still contains a previously encoded interim response + * not yet emitted, buf head must not be adjusted and frame length is + * encoded as a 4-bytes varint so that responses are contiguous. + */ + if (!b_data(res)) { + frame_length_size = quic_int_getsize(b_data(&headers_buf)); + res->head += 4 - frame_length_size; + b_putchr(res, 0x01); /* h3 HEADERS frame type */ + b_quic_enc_int(res, b_data(&headers_buf), 0); + } + else { + b_putchr(res, 0x01); /* h3 HEADERS frame type */ + b_quic_enc_int(res, b_data(&headers_buf), 4); + } b_add(res, b_data(&headers_buf)); ret = 0;