]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: mux-h2: Reject trailers received on a tunneled stream
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 11 Sep 2026 15:43:13 +0000 (17:43 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 18 Sep 2026 15:05:18 +0000 (17:05 +0200)
For a tunneled stream, the end of the HTTP message is reported just after
the headers. It is an "early" EOM: from the HTTP point of view, the message
is finished and only tunneled data may be exchanged then.

However, a HEADERS frame received after the initial one on such a stream
was still handled as a trailers block. The trailers were thus pushed in an
HTTP message already ended, leading to an invalid HTX message where blocks
are inserted after the end of the message. The stream is then aborted when
the outgoing multiplexer handles the unexpected block. So a client is able
to kill its own tunnel by sending a bogus HEADERS frame.

Such a frame is now rejected with a stream error (PROTOCOL_ERROR). The test
is performed in h2c_dec_hdrs(), at the only place where a HEADERS frame is
identified as a trailers block. This way, both directions are covered. The
aborted tunnel attempts are also covered because the early EOM was reported
in that case too.

This patch should be backported to all stable versions. Note that
h2c_report_glitch() only exists since 3.0. The corresponding line must be
removed for older versions.

src/mux_h2.c

index ab86284036562430f98368018750878a8d66882e..21cd83b3e8a96c77825deb5aa0a88755b9a6043a 100644 (file)
@@ -6475,6 +6475,29 @@ next_frame:
 
  trailers:
        /* This is the last HEADERS frame hence a trailer */
+       if ((*flags & H2_SF_BODY_TUNNEL) ||
+           (!(h2c->flags & H2_CF_IS_BACK) && (*flags & H2_SF_TUNNEL_ABRT))) {
+               /* No trailers are expected on a tunneled stream. The end of the
+                * HTTP message was already reported, just after the headers, so
+                * only tunneled data may be received now.
+                *
+                * On the request side, this remains true when the tunnel attempt
+                * was aborted, because the early end of the message was already
+                * reported for the CONNECT request. But not on the response
+                * side: an aborted tunnel means the response is a regular one,
+                * with a payload and thus possibly with trailers. In this case,
+                * no early end of the message was reported.
+                *
+                * Note: Regarding the RFC, it is not strickly speaking
+                * forbidden. But internally, we don't know how to handle such
+                * frames. So better to reject them.
+                */
+               h2c_report_glitch(h2c, 1, "rcvd trailers on a tunneled stream");
+               TRACE_STATE("rcvd trailers on a tunneled stream", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2S_ERR|H2_EV_PROTO_ERR, h2c->conn);
+               HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
+               goto fail;
+       }
+
        if (!(h2c->dff & H2_F_HEADERS_END_STREAM)) {
                /* It's a trailer but it's missing ES flag */
                h2c_report_glitch(h2c, 1, "missing EH on trailers frame");