]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: hpack: encode long methods and schemes using the long form
authorWilly Tarreau <w@1wt.eu>
Thu, 6 Aug 2026 07:19:58 +0000 (09:19 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 6 Aug 2026 15:56:22 +0000 (17:56 +0200)
hpack_encode_scheme() and hpack_encode_method() document that they're
limited to 127 chars since they only rely on hpack_encode_short_idx()
for literals, but the H2 mux doesn't check this. This results in only
the bytes modulo 256 being advertised on a backend connection. As such
the remaining bytes will be confused with other HPACK opcodes. Note
that in practice, the ability to exploit this to inject headers from a
front H1 connection is very limited due to the strict alphabet enabled
in schemes and methods which limits usable codes to literal headers with
indexing for absent pseudo-headers (i.e. no :scheme, :method, :path,
possibly one :authority when none is provided, but it must then match
the host), and whose value will be of 43 chars minimum.

The real impact in practice is to provoke protocol errors and cause
shared H2 backend connections to be abruptly closed in environments
using http-reuse always.

Let's simply make both encoders fall back to hpack_encode_long_idx()
for value larger than 127 bytes, like hpack_encode_path() does. The
bug has been present since 1.9 with commit 39c80ebff ("MINOR: hpack:
provide a function to encode an HTTP method").

Reported-by: Claude (ANT-2026-9SVV6W3Q)
This fix must be backported to all stable versions.

include/haproxy/hpack-enc.h

index 063d5c62e72da5497472a120a17b1423d46ec8b6..01246dd22373290cf93a5c0f2776bb06f06bd0a6 100644 (file)
@@ -202,10 +202,9 @@ static inline int hpack_encode_str_status(struct buffer *out, unsigned int statu
 /* Tries to encode a :method pseudo-header with the method in <meth>, which
  * also exists as a string in <str>, into the aligned buffer <out>. Returns
  * non-zero on success or 0 on failure (buffer full). The caller is responsible
- * for ensuring that the string matches <meth>, that it's smaller than 127
- * bytes, and that the buffer is aligned. If <meth> is unknown then using
- * HTTP_METH_OTHER will lead to the string being encoded as a literal. It's
- * inlined because it's easily optimizable.
+ * for ensuring that the string matches <meth> and that the buffer is aligned.
+ * If <meth> is unknown then using HTTP_METH_OTHER will lead to the string
+ * being encoded as a literal. It's inlined because it's easily optimizable.
  */
 static inline int hpack_encode_method(struct buffer *out, enum http_meth_t meth, struct ist str)
 {
@@ -213,18 +212,19 @@ static inline int hpack_encode_method(struct buffer *out, enum http_meth_t meth,
                out->area[out->data++] = 0x82; // indexed field : idx[02]=(":method", "GET")
        else if (out->data < out->size && meth == HTTP_METH_POST)
                out->area[out->data++] = 0x83; // indexed field : idx[03]=(":method", "POST")
-       else
+       else if (str.len < 127)
                return hpack_encode_short_idx(out, 2, str); // name=":method" (idx 2)
+       else
+               return hpack_encode_long_idx(out, 2, str); // name=":method" (idx 2)
        return 1;
 }
 
 /* Tries to encode a :scheme pseudo-header with the scheme in <scheme>, into
  * the aligned buffer <out>. Returns non-zero on success or 0 on failure
  * (buffer full). Only "http" and "https" are recognized and handled as indexed
- * values, others are turned into short literals. The caller is responsible for
- * ensuring that the scheme is smaller than 127 bytes, and that the buffer is
- * aligned. Normally the compiler will detect constant strings in the comparison
- * if the code remains inlined.
+ * values, others are turned into literals. The caller is responsible for
+ * ensuring that the buffer is aligned. Normally the compiler will detect
+ * constant strings in the comparison if the code remains inlined.
  */
 static inline int hpack_encode_scheme(struct buffer *out, struct ist scheme)
 {
@@ -232,8 +232,10 @@ static inline int hpack_encode_scheme(struct buffer *out, struct ist scheme)
                out->area[out->data++] = 0x87; // indexed field : idx[07]=(":scheme", "https")
        else if (out->data < out->size && isteq(scheme, ist("http")))
                out->area[out->data++] = 0x86; // indexed field : idx[06]=(":scheme", "http")
-       else
+       else if (scheme.len < 127)
                return hpack_encode_short_idx(out, 6, scheme); // name=":scheme" (idx 6)
+       else
+               return hpack_encode_long_idx(out, 6, scheme); // name=":scheme" (idx 6)
        return 1;
 }