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.
This commit is contained in:
Amaury Denoyelle 2025-10-21 15:06:39 +02:00
parent 18ece2b424
commit bece704128

View File

@ -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
*/
/* 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;