From a8b478fa6e57eb52dc9a1f95e4d1f18d05842421 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <w@1wt.eu>
Date: Tue, 10 Aug 2021 16:30:55 +0200
Subject: BUG/MAJOR: h2: verify that :path starts with a '/' before
 concatenating it
MIME-Version: 1.0
Content-Type: text/plain; charset=latin1
Content-Transfer-Encoding: 8bit

Tim Düsterhus found that while the H2 path is checked for non-emptiness,
invalid chars and '*', a test is missing to verify that except for '*',
it always starts with exactly one '/'. During the reconstruction of the
full URI when passing to HTX, this allows to affect the apparent authority
by appending a port number or a suffix name.

This only affects H2-to-H2 communications, as H2-to-H1 do not use the
authority. Like for previous fix, the following rule installed in the
frontend or backend is sufficient to renormalize the internal URI:

    http-request set-header host %[req.hdr(host)]

This needs to be backported to 2.2, since earlier versions do not rebuild
a full URI using the authority and will fail on the malformed path at the
HTTP layer.

(cherry picked from commit d3b22b75025246e81ff8d0c78837d4b89d7cf8f8)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit 2360306269ff65420cba7c847687a774b1025ab5)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit c99c5cd3588a28978cd065abc74508fe81a93a40)
Signed-off-by: Willy Tarreau <w@1wt.eu>
---
 src/h2.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/src/h2.c b/src/h2.c
index bf44611a2..264848812 100644
--- a/src/h2.c
+++ b/src/h2.c
@@ -234,6 +234,22 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
 			htx->flags |= HTX_FL_PARSING_ERROR;
 	}
 
+	if (fields & H2_PHDR_FND_PATH) {
+		/* 7540#8.1.2.3: :path must not be empty, and must be either
+		 * '*' or an RFC3986 "path-absolute" starting with a "/" but
+		 * not with "//".
+		 */
+		if (unlikely(!phdr[H2_PHDR_IDX_PATH].len))
+			goto fail;
+		else if (unlikely(phdr[H2_PHDR_IDX_PATH].ptr[0] != '/')) {
+			if (!isteq(phdr[H2_PHDR_IDX_PATH], ist("*")))
+				goto fail;
+		}
+		else if (phdr[H2_PHDR_IDX_PATH].len > 1 &&
+			 phdr[H2_PHDR_IDX_PATH].ptr[1] == '/')
+			goto fail;
+	}
+
 	if (!(flags & HTX_SL_F_HAS_SCHM)) {
 		/* no scheme, use authority only (CONNECT) */
 		uri = phdr[H2_PHDR_IDX_AUTH];
@@ -244,9 +260,6 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
 		 * use the trash to concatenate them since all of them MUST fit
 		 * in a bufsize since it's where they come from.
 		 */
-		if (unlikely(!phdr[H2_PHDR_IDX_PATH].len))
-			goto fail;   // 7540#8.1.2.3: :path must not be empty
-
 		uri = ist2bin(trash.area, phdr[H2_PHDR_IDX_SCHM]);
 		istcat(&uri, ist("://"), trash.size);
 		istcat(&uri, phdr[H2_PHDR_IDX_AUTH], trash.size);
-- 
2.28.0

