diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:35:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:35:12 +0000 |
commit | 89057599f4791f03c1ee5de836fbe2b5ea434aa9 (patch) | |
tree | 09ef6df146509f94541c1afe9cbd78b7f75e81e7 /debian/patches | |
parent | Adding upstream version 2.6.12. (diff) | |
download | haproxy-89057599f4791f03c1ee5de836fbe2b5ea434aa9.tar.xz haproxy-89057599f4791f03c1ee5de836fbe2b5ea434aa9.zip |
Adding debian version 2.6.12-1+deb12u1.debian/2.6.12-1+deb12u1debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/patches')
18 files changed, 1396 insertions, 0 deletions
diff --git a/debian/patches/BUG-MAJOR-h3-reject-header-values-containing-invalid.patch b/debian/patches/BUG-MAJOR-h3-reject-header-values-containing-invalid.patch new file mode 100644 index 0000000..11ae6f8 --- /dev/null +++ b/debian/patches/BUG-MAJOR-h3-reject-header-values-containing-invalid.patch @@ -0,0 +1,100 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 17:18:27 +0200 +Subject: BUG/MAJOR: h3: reject header values containing invalid chars +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=20a35c4d505475215122d37405ce173075338032 + +In practice it's exactly the same for h3 as 54f53ef7c ("BUG/MAJOR: h2: +reject header values containing invalid chars") was for h2: we must +make sure never to accept NUL/CR/LF in any header value because this +may be used to construct splitted headers on the backend. Hence we +apply the same solution. Here pseudo-headers, headers and trailers are +checked separately, which explains why we have 3 locations instead of +2 for h2 (+1 for response which we don't have here). + +This is marked major for consistency and due to the impact if abused, +but the reality is that at the time of writing, this problem is limited +by the scarcity of the tools which would permit to build such a request +in the first place. But this may change over time. + +This must be backported to 2.6. This depends on the following commit +that exposes the filtering function: + + REORG: http: move has_forbidden_char() from h2.c to http.h + +(cherry picked from commit d13a80abb7c1badaa42045c37cfff79f24f05726) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 0404bf14c900d6ac879ec432a198435e0741d835) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit f58b63af68f239464c29e698a34f08ff3eef862f) + [ad: no http/3 trailer support in 2.6] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + src/h3.c | 38 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 38 insertions(+) + +diff --git a/src/h3.c b/src/h3.c +index 9b4bc6f096d7..b42d41647e4e 100644 +--- a/src/h3.c ++++ b/src/h3.c +@@ -401,6 +401,7 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, + struct ist scheme = IST_NULL, authority = IST_NULL; + int hdr_idx, ret; + int cookie = -1, last_cookie = -1, i; ++ const char *ctl; + + /* RFC 9114 4.1.2. Malformed Requests and Responses + * +@@ -464,6 +465,24 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, + if (!istmatch(list[hdr_idx].n, ist(":"))) + break; + ++ /* RFC 9114 10.3 Intermediary-Encapsulation Attacks ++ * ++ * While most values that can be encoded will not alter field ++ * parsing, carriage return (ASCII 0x0d), line feed (ASCII 0x0a), ++ * and the null character (ASCII 0x00) might be exploited by an ++ * attacker if they are translated verbatim. Any request or ++ * response that contains a character not permitted in a field ++ * value MUST be treated as malformed ++ */ ++ ++ /* look for forbidden control characters in the pseudo-header value */ ++ ctl = ist_find_ctl(list[hdr_idx].v); ++ if (unlikely(ctl) && http_header_has_forbidden_char(list[hdr_idx].v, ctl)) { ++ TRACE_ERROR("control character present in pseudo-header value", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs); ++ len = -1; ++ goto out; ++ } ++ + /* pseudo-header. Malformed name with uppercase character or + * invalid token will be rejected in the else clause. + */ +@@ -561,6 +580,25 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, + } + } + ++ ++ /* RFC 9114 10.3 Intermediary-Encapsulation Attacks ++ * ++ * While most values that can be encoded will not alter field ++ * parsing, carriage return (ASCII 0x0d), line feed (ASCII 0x0a), ++ * and the null character (ASCII 0x00) might be exploited by an ++ * attacker if they are translated verbatim. Any request or ++ * response that contains a character not permitted in a field ++ * value MUST be treated as malformed ++ */ ++ ++ /* look for forbidden control characters in the header value */ ++ ctl = ist_find_ctl(list[hdr_idx].v); ++ if (unlikely(ctl) && http_header_has_forbidden_char(list[hdr_idx].v, ctl)) { ++ TRACE_ERROR("control character present in header value", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs); ++ len = -1; ++ goto out; ++ } ++ + if (isteq(list[hdr_idx].n, ist("cookie"))) { + http_cookie_register(list, hdr_idx, &cookie, &last_cookie); + ++hdr_idx; +-- +2.43.0 + diff --git a/debian/patches/BUG-MAJOR-http-reject-any-empty-content-length-heade.patch b/debian/patches/BUG-MAJOR-http-reject-any-empty-content-length-heade.patch new file mode 100644 index 0000000..fb8bcb0 --- /dev/null +++ b/debian/patches/BUG-MAJOR-http-reject-any-empty-content-length-heade.patch @@ -0,0 +1,274 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Wed, 9 Aug 2023 08:32:48 +0200 +Subject: BUG/MAJOR: http: reject any empty content-length header value +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=d17c50010d591d1c070e1cb0567a06032d8869e9 +Bug-Debian: https://bugs.debian.org/1043502 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2023-40225 + +The content-length header parser has its dedicated function, in order +to take extreme care about invalid, unparsable, or conflicting values. +But there's a corner case in it, by which it stops comparing values +when reaching the end of the header. This has for a side effect that +an empty value or a value that ends with a comma does not deserve +further analysis, and it acts as if the header was absent. + +While this is not necessarily a problem for the value ending with a +comma as it will be cause a header folding and will disappear, it is a +problem for the first isolated empty header because this one will not +be recontructed when next ones are seen, and will be passed as-is to the +backend server. A vulnerable HTTP/1 server hosted behind haproxy that +would just use this first value as "0" and ignore the valid one would +then not be protected by haproxy and could be attacked this way, taking +the payload for an extra request. + +In field the risk depends on the server. Most commonly used servers +already have safe content-length parsers, but users relying on haproxy +to protect a known-vulnerable server might be at risk (and the risk of +a bug even in a reputable server should never be dismissed). + +A configuration-based work-around consists in adding the following rule +in the frontend, to explicitly reject requests featuring an empty +content-length header that would have not be folded into an existing +one: + + http-request deny if { hdr_len(content-length) 0 } + +The real fix consists in adjusting the parser so that it always expects a +value at the beginning of the header or after a comma. It will now reject +requests and responses having empty values anywhere in the C-L header. + +This needs to be backported to all supported versions. Note that the +modification was made to functions h1_parse_cont_len_header() and +http_parse_cont_len_header(). Prior to 2.8 the latter was in +h2_parse_cont_len_header(). One day the two should be refused but the +former is also used by Lua. + +The HTTP messaging reg-tests were completed to test these cases. + +Thanks to Ben Kallus of Dartmouth College and Narf Industries for +reporting this! (this is in GH #2237). + +(cherry picked from commit 6492f1f29d738457ea9f382aca54537f35f9d856) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit a32f99f6f991d123ea3e307bf8aa63220836d365) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 65921ee12d88e9fb1fa9f6cd8198fd64b3a3f37f) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + reg-tests/http-messaging/h1_to_h1.vtc | 26 ++++++++++++ + reg-tests/http-messaging/h2_to_h1.vtc | 60 +++++++++++++++++++++++++++ + src/h1.c | 20 +++++++-- + src/http.c | 20 +++++++-- + 4 files changed, 120 insertions(+), 6 deletions(-) + +diff --git a/reg-tests/http-messaging/h1_to_h1.vtc b/reg-tests/http-messaging/h1_to_h1.vtc +index 0d6536698608..67aba1440949 100644 +--- a/reg-tests/http-messaging/h1_to_h1.vtc ++++ b/reg-tests/http-messaging/h1_to_h1.vtc +@@ -273,3 +273,29 @@ client c3h1 -connect ${h1_feh1_sock} { + # arrive here. + expect_close + } -run ++ ++client c4h1 -connect ${h1_feh1_sock} { ++ # this request is invalid and advertises an invalid C-L ending with an ++ # empty value, which results in a stream error. ++ txreq \ ++ -req "GET" \ ++ -url "/test31.html" \ ++ -hdr "content-length: 0," \ ++ -hdr "connection: close" ++ rxresp ++ expect resp.status == 400 ++ expect_close ++} -run ++ ++client c5h1 -connect ${h1_feh1_sock} { ++ # this request is invalid and advertises an empty C-L, which results ++ # in a stream error. ++ txreq \ ++ -req "GET" \ ++ -url "/test41.html" \ ++ -hdr "content-length:" \ ++ -hdr "connection: close" ++ rxresp ++ expect resp.status == 400 ++ expect_close ++} -run +diff --git a/reg-tests/http-messaging/h2_to_h1.vtc b/reg-tests/http-messaging/h2_to_h1.vtc +index 852ee4caf9dd..5c8c8214314b 100644 +--- a/reg-tests/http-messaging/h2_to_h1.vtc ++++ b/reg-tests/http-messaging/h2_to_h1.vtc +@@ -10,6 +10,8 @@ barrier b1 cond 2 -cyclic + barrier b2 cond 2 -cyclic + barrier b3 cond 2 -cyclic + barrier b4 cond 2 -cyclic ++barrier b5 cond 2 -cyclic ++barrier b6 cond 2 -cyclic + + server s1 { + rxreq +@@ -31,6 +33,12 @@ server s1 { + + barrier b4 sync + # the next request is never received ++ ++ barrier b5 sync ++ # the next request is never received ++ ++ barrier b6 sync ++ # the next request is never received + } -repeat 2 -start + + haproxy h1 -conf { +@@ -120,6 +128,32 @@ client c1h2 -connect ${h1_feh2_sock} { + txdata -data "this is sent and ignored" + rxrst + } -run ++ ++ # fifth request is invalid and advertises an invalid C-L ending with an ++ # empty value, which results in a stream error. ++ stream 9 { ++ barrier b5 sync ++ txreq \ ++ -req "GET" \ ++ -scheme "https" \ ++ -url "/test5.html" \ ++ -hdr "content-length" "0," \ ++ -nostrend ++ rxrst ++ } -run ++ ++ # sixth request is invalid and advertises an empty C-L, which results ++ # in a stream error. ++ stream 11 { ++ barrier b6 sync ++ txreq \ ++ -req "GET" \ ++ -scheme "https" \ ++ -url "/test6.html" \ ++ -hdr "content-length" "" \ ++ -nostrend ++ rxrst ++ } -run + } -run + + # HEAD requests : don't work well yet +@@ -262,4 +296,30 @@ client c3h2 -connect ${h1_feh2_sock} { + txdata -data "this is sent and ignored" + rxrst + } -run ++ ++ # fifth request is invalid and advertises invalid C-L ending with an ++ # empty value, which results in a stream error. ++ stream 9 { ++ barrier b5 sync ++ txreq \ ++ -req "POST" \ ++ -scheme "https" \ ++ -url "/test25.html" \ ++ -hdr "content-length" "0," \ ++ -nostrend ++ rxrst ++ } -run ++ ++ # sixth request is invalid and advertises an empty C-L, which results ++ # in a stream error. ++ stream 11 { ++ barrier b6 sync ++ txreq \ ++ -req "POST" \ ++ -scheme "https" \ ++ -url "/test26.html" \ ++ -hdr "content-length" "" \ ++ -nostrend ++ rxrst ++ } -run + } -run +diff --git a/src/h1.c b/src/h1.c +index 88a54c4a593d..126f23cc7376 100644 +--- a/src/h1.c ++++ b/src/h1.c +@@ -34,13 +34,20 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) + int not_first = !!(h1m->flags & H1_MF_CLEN); + struct ist word; + +- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment ++ word.ptr = value->ptr; + e = value->ptr + value->len; + +- while (++word.ptr < e) { ++ while (1) { ++ if (word.ptr >= e) { ++ /* empty header or empty value */ ++ goto fail; ++ } ++ + /* skip leading delimiter and blanks */ +- if (unlikely(HTTP_IS_LWS(*word.ptr))) ++ if (unlikely(HTTP_IS_LWS(*word.ptr))) { ++ word.ptr++; + continue; ++ } + + /* digits only now */ + for (cl = 0, n = word.ptr; n < e; n++) { +@@ -79,6 +86,13 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value) + h1m->flags |= H1_MF_CLEN; + h1m->curr_len = h1m->body_len = cl; + *value = word; ++ ++ /* Now either n==e and we're done, or n points to the comma, ++ * and we skip it and continue. ++ */ ++ if (n++ == e) ++ break; ++ + word.ptr = n; + } + /* here we've reached the end with a single value or a series of +diff --git a/src/http.c b/src/http.c +index edf4744553a2..a33c673c11da 100644 +--- a/src/http.c ++++ b/src/http.c +@@ -707,13 +707,20 @@ int http_parse_cont_len_header(struct ist *value, unsigned long long *body_len, + struct ist word; + int check_prev = not_first; + +- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment ++ word.ptr = value->ptr; + e = value->ptr + value->len; + +- while (++word.ptr < e) { ++ while (1) { ++ if (word.ptr >= e) { ++ /* empty header or empty value */ ++ goto fail; ++ } ++ + /* skip leading delimiter and blanks */ +- if (unlikely(HTTP_IS_LWS(*word.ptr))) ++ if (unlikely(HTTP_IS_LWS(*word.ptr))) { ++ word.ptr++; + continue; ++ } + + /* digits only now */ + for (cl = 0, n = word.ptr; n < e; n++) { +@@ -751,6 +758,13 @@ int http_parse_cont_len_header(struct ist *value, unsigned long long *body_len, + /* OK, store this result as the one to be indexed */ + *body_len = cl; + *value = word; ++ ++ /* Now either n==e and we're done, or n points to the comma, ++ * and we skip it and continue. ++ */ ++ if (n++ == e) ++ break; ++ + word.ptr = n; + check_prev = 1; + } +-- +2.43.0 + diff --git a/debian/patches/BUG-MINOR-h1-do-not-accept-as-part-of-the-URI-compon.patch b/debian/patches/BUG-MINOR-h1-do-not-accept-as-part-of-the-URI-compon.patch new file mode 100644 index 0000000..02d1e74 --- /dev/null +++ b/debian/patches/BUG-MINOR-h1-do-not-accept-as-part-of-the-URI-compon.patch @@ -0,0 +1,118 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 16:17:22 +0200 +Subject: BUG/MINOR: h1: do not accept '#' as part of the URI component +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=832b672eee54866c7a42a1d46078cc9ae0d544d9 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2023-45539 + +Seth Manesse and Paul Plasil reported that the "path" sample fetch +function incorrectly accepts '#' as part of the path component. This +can in some cases lead to misrouted requests for rules that would apply +on the suffix: + + use_backend static if { path_end .png .jpg .gif .css .js } + +Note that this behavior can be selectively configured using +"normalize-uri fragment-encode" and "normalize-uri fragment-strip". + +The problem is that while the RFC says that this '#' must never be +emitted, as often it doesn't suggest how servers should handle it. A +diminishing number of servers still do accept it and trim it silently, +while others are rejecting it, as indicated in the conversation below +with other implementers: + + https://lists.w3.org/Archives/Public/ietf-http-wg/2023JulSep/0070.html + +Looking at logs from publicly exposed servers, such requests appear at +a rate of roughly 1 per million and only come from attacks or poorly +written web crawlers incorrectly following links found on various pages. + +Thus it looks like the best solution to this problem is to simply reject +such ambiguous requests by default, and include this in the list of +controls that can be disabled using "option accept-invalid-http-request". + +We're already rejecting URIs containing any control char anyway, so we +should also reject '#'. + +In the H1 parser for the H1_MSG_RQURI state, there is an accelerated +parser for bytes 0x21..0x7e that has been tightened to 0x24..0x7e (it +should not impact perf since 0x21..0x23 are not supposed to appear in +a URI anyway). This way '#' falls through the fine-grained filter and +we can add the special case for it also conditionned by a check on the +proxy's option "accept-invalid-http-request", with no overhead for the +vast majority of valid URIs. Here this information is available through +h1m->err_pos that's set to -2 when the option is here (so we don't need +to change the API to expose the proxy). Example with a trivial GET +through netcat: + + [08/Aug/2023:16:16:52.651] frontend layer1 (#2): invalid request + backend <NONE> (#-1), server <NONE> (#-1), event #0, src 127.0.0.1:50812 + buffer starts at 0 (including 0 out), 16361 free, + len 23, wraps at 16336, error at position 7 + H1 connection flags 0x00000000, H1 stream flags 0x00000810 + H1 msg state MSG_RQURI(4), H1 msg flags 0x00001400 + H1 chunk len 0 bytes, H1 body len 0 bytes : + + 00000 GET /aa#bb HTTP/1.0\r\n + 00021 \r\n + +This should be progressively backported to all stable versions along with +the following patch: + + REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests + +Similar fixes for h2 and h3 will come in followup patches. + +Thanks to Seth Manesse and Paul Plasil for reporting this problem with +detailed explanations. + +(cherry picked from commit 2eab6d354322932cfec2ed54de261e4347eca9a6) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 9bf75c8e22a8f2537f27c557854a8803087046d0) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 9facd01c9ac85fe9bcb331594b80fa08e7406552) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + src/h1.c | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +diff --git a/src/h1.c b/src/h1.c +index 126f23cc7376..92ec96bfe19e 100644 +--- a/src/h1.c ++++ b/src/h1.c +@@ -565,13 +565,13 @@ int h1_headers_to_hdr_list(char *start, const char *stop, + case H1_MSG_RQURI: + http_msg_rquri: + #ifdef HA_UNALIGNED_LE +- /* speedup: skip bytes not between 0x21 and 0x7e inclusive */ ++ /* speedup: skip bytes not between 0x24 and 0x7e inclusive */ + while (ptr <= end - sizeof(int)) { +- int x = *(int *)ptr - 0x21212121; ++ int x = *(int *)ptr - 0x24242424; + if (x & 0x80808080) + break; + +- x -= 0x5e5e5e5e; ++ x -= 0x5b5b5b5b; + if (!(x & 0x80808080)) + break; + +@@ -583,8 +583,15 @@ int h1_headers_to_hdr_list(char *start, const char *stop, + goto http_msg_ood; + } + http_msg_rquri2: +- if (likely((unsigned char)(*ptr - 33) <= 93)) /* 33 to 126 included */ ++ if (likely((unsigned char)(*ptr - 33) <= 93)) { /* 33 to 126 included */ ++ if (*ptr == '#') { ++ if (h1m->err_pos < -1) /* PR_O2_REQBUG_OK not set */ ++ goto invalid_char; ++ if (h1m->err_pos == -1) /* PR_O2_REQBUG_OK set: just log */ ++ h1m->err_pos = ptr - start + skip; ++ } + EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rquri2, http_msg_ood, state, H1_MSG_RQURI); ++ } + + if (likely(HTTP_IS_SPHT(*ptr))) { + sl.rq.u.len = ptr - sl.rq.u.ptr; +-- +2.43.0 + diff --git a/debian/patches/BUG-MINOR-h2-reject-more-chars-from-the-path-pseudo-.patch b/debian/patches/BUG-MINOR-h2-reject-more-chars-from-the-path-pseudo-.patch new file mode 100644 index 0000000..c5b2b9d --- /dev/null +++ b/debian/patches/BUG-MINOR-h2-reject-more-chars-from-the-path-pseudo-.patch @@ -0,0 +1,68 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 15:40:49 +0200 +Subject: BUG/MINOR: h2: reject more chars from the :path pseudo header +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=c8e07f2fd8b5462527f102f7145d6027c0d041da + +This is the h2 version of this previous fix: + + BUG/MINOR: h1: do not accept '#' as part of the URI component + +In addition to the current NUL/CR/LF, this will also reject all other +control chars, the space and '#' from the :path pseudo-header, to avoid +taking the '#' for a part of the path. It's still possible to fall back +to the previous behavior using "option accept-invalid-http-request". + +This patch modifies the request parser to change the ":path" pseudo header +validation function with a new one that rejects 0x00-0x1F (control chars), +space and '#'. This way such chars will be dropped early in the chain, and +the search for '#' doesn't incur a second pass over the header's value. + +This should be progressively backported to stable versions, along with the +following commits it relies on: + + REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests + REORG: http: move has_forbidden_char() from h2.c to http.h + MINOR: ist: add new function ist_find_range() to find a character range + MINOR: http: add new function http_path_has_forbidden_char() + MINOR: h2: pass accept-invalid-http-request down the request parser + +(cherry picked from commit b3119d4fb4588087e2483a80b01d322683719e29) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 462a8600ce9e478573a957e046b446a7dcffd286) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 648e59e30723b8fd4e71aab02cb679f6ea7446e7) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + src/h2.c | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +diff --git a/src/h2.c b/src/h2.c +index cf42b7a5610e..67a443661c3e 100644 +--- a/src/h2.c ++++ b/src/h2.c +@@ -337,11 +337,18 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms + } + + /* RFC7540#10.3: intermediaries forwarding to HTTP/1 must take care of +- * rejecting NUL, CR and LF characters. ++ * rejecting NUL, CR and LF characters. For :path we reject all CTL ++ * chars, spaces, and '#'. + */ +- ctl = ist_find_ctl(list[idx].v); +- if (unlikely(ctl) && http_header_has_forbidden_char(list[idx].v, ctl)) +- goto fail; ++ if (phdr == H2_PHDR_IDX_PATH && !relaxed) { ++ ctl = ist_find_range(list[idx].v, 0, '#'); ++ if (unlikely(ctl) && http_path_has_forbidden_char(list[idx].v, ctl)) ++ goto fail; ++ } else { ++ ctl = ist_find_ctl(list[idx].v); ++ if (unlikely(ctl) && http_header_has_forbidden_char(list[idx].v, ctl)) ++ goto fail; ++ } + + if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) { + /* insert a pseudo header by its index (in phdr) and value (in value) */ +-- +2.43.0 + diff --git a/debian/patches/BUG-MINOR-h3-reject-more-chars-from-the-path-pseudo-.patch b/debian/patches/BUG-MINOR-h3-reject-more-chars-from-the-path-pseudo-.patch new file mode 100644 index 0000000..cbc086c --- /dev/null +++ b/debian/patches/BUG-MINOR-h3-reject-more-chars-from-the-path-pseudo-.patch @@ -0,0 +1,71 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 17:54:26 +0200 +Subject: BUG/MINOR: h3: reject more chars from the :path pseudo header +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=eacaa76e7b0e4182dfd17e1e7ca8c02c1cdab72c + +This is the h3 version of this previous fix: + + BUG/MINOR: h2: reject more chars from the :path pseudo header + +In addition to the current NUL/CR/LF, this will also reject all other +control chars, the space and '#' from the :path pseudo-header, to avoid +taking the '#' for a part of the path. It's still possible to fall back +to the previous behavior using "option accept-invalid-http-request". + +Here the :path header value is scanned a second time to look for +forbidden chars because we don't know upfront if we're dealing with a +path header field or another one. This is no big deal anyway for now. + +This should be progressively backported to 2.6, along with the +following commits it relies on (the same as for h2): + + REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests + REORG: http: move has_forbidden_char() from h2.c to http.h + MINOR: ist: add new function ist_find_range() to find a character range + MINOR: http: add new function http_path_has_forbidden_char() + +(cherry picked from commit 2e97857a845540887a92029a566deb5b51f61d0b) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 96dfea858edab8f1f63fa6e4df43f505b81fdad9) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 97c15782afd9c70281ff0c72971485227494cc12) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + src/h3.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/src/h3.c b/src/h3.c +index b42d41647e4e..e519fb4432e7 100644 +--- a/src/h3.c ++++ b/src/h3.c +@@ -402,6 +402,7 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, + int hdr_idx, ret; + int cookie = -1, last_cookie = -1, i; + const char *ctl; ++ int relaxed = !!(h3c->qcc->proxy->options2 & PR_O2_REQBUG_OK); + + /* RFC 9114 4.1.2. Malformed Requests and Responses + * +@@ -500,6 +501,19 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf, + len = -1; + goto out; + } ++ ++ if (!relaxed) { ++ /* we need to reject any control chars or '#' from the path, ++ * unless option accept-invalid-http-request is set. ++ */ ++ ctl = ist_find_range(list[hdr_idx].v, 0, '#'); ++ if (unlikely(ctl) && http_path_has_forbidden_char(list[hdr_idx].v, ctl)) { ++ TRACE_ERROR("forbidden character in ':path' pseudo-header", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs); ++ len = -1; ++ goto out; ++ } ++ } ++ + path = list[hdr_idx].v; + } + else if (isteq(list[hdr_idx].n, ist(":scheme"))) { +-- +2.43.0 + diff --git a/debian/patches/DOC-clarify-the-handling-of-URL-fragments-in-request.patch b/debian/patches/DOC-clarify-the-handling-of-URL-fragments-in-request.patch new file mode 100644 index 0000000..8730e9a --- /dev/null +++ b/debian/patches/DOC-clarify-the-handling-of-URL-fragments-in-request.patch @@ -0,0 +1,76 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 19:35:25 +0200 +Subject: DOC: clarify the handling of URL fragments in requests +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=c47814a58ec153a526e8e9e822cda6e66cef5cc2 + +We indicate in path/pathq/url that they may contain '#' if the frontend +is configured with "option accept-invalid-http-request", and that option +mentions the fragment as well. + +(cherry picked from commit 7ab4949ef107a7088777f954de800fe8cf727796) + [ad: backported as a companion to BUG/MINOR: h1: do not accept '#' as + part of the URI component] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 965fb74eb180ab4f275ef907e018128e7eee0e69) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit e9903d6073ce9ff0ed8b304700e9d2b435ed8050) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + doc/configuration.txt | 20 +++++++++++++++++--- + 1 file changed, 17 insertions(+), 3 deletions(-) + +diff --git a/doc/configuration.txt b/doc/configuration.txt +index 7219c489ef9a..c01abb2d0a66 100644 +--- a/doc/configuration.txt ++++ b/doc/configuration.txt +@@ -8609,6 +8609,8 @@ no option accept-invalid-http-request + option also relaxes the test on the HTTP version, it allows HTTP/0.9 requests + to pass through (no version specified), as well as different protocol names + (e.g. RTSP), and multiple digits for both the major and the minor version. ++ Finally, this option also allows incoming URLs to contain fragment references ++ ('#' after the path). + + This option should never be enabled by default as it hides application bugs + and open security breaches. It should only be deployed after a problem has +@@ -20991,7 +20993,11 @@ path : string + information from databases and keep them in caches. Note that with outgoing + caches, it would be wiser to use "url" instead. With ACLs, it's typically + used to match exact file names (e.g. "/login.php"), or directory parts using +- the derivative forms. See also the "url" and "base" fetch methods. ++ the derivative forms. See also the "url" and "base" fetch methods. Please ++ note that any fragment reference in the URI ('#' after the path) is strictly ++ forbidden by the HTTP standard and will be rejected. However, if the frontend ++ receiving the request has "option accept-invalid-http-request", then this ++ fragment part will be accepted and will also appear in the path. + + ACL derivatives : + path : exact string match +@@ -21009,7 +21015,11 @@ pathq : string + relative URI, excluding the scheme and the authority part, if any. Indeed, + while it is the common representation for an HTTP/1.1 request target, in + HTTP/2, an absolute URI is often used. This sample fetch will return the same +- result in both cases. ++ result in both cases. Please note that any fragment reference in the URI ('#' ++ after the path) is strictly forbidden by the HTTP standard and will be ++ rejected. However, if the frontend receiving the request has "option ++ accept-invalid-http-request", then this fragment part will be accepted and ++ will also appear in the path. + + query : string + This extracts the request's query string, which starts after the first +@@ -21242,7 +21252,11 @@ url : string + "path" is preferred over using "url", because clients may send a full URL as + is normally done with proxies. The only real use is to match "*" which does + not match in "path", and for which there is already a predefined ACL. See +- also "path" and "base". ++ also "path" and "base". Please note that any fragment reference in the URI ++ ('#' after the path) is strictly forbidden by the HTTP standard and will be ++ rejected. However, if the frontend receiving the request has "option ++ accept-invalid-http-request", then this fragment part will be accepted and ++ will also appear in the url. + + ACL derivatives : + url : exact string match +-- +2.43.0 + diff --git a/debian/patches/MINOR-h2-pass-accept-invalid-http-request-down-the-r.patch b/debian/patches/MINOR-h2-pass-accept-invalid-http-request-down-the-r.patch new file mode 100644 index 0000000..dac7216 --- /dev/null +++ b/debian/patches/MINOR-h2-pass-accept-invalid-http-request-down-the-r.patch @@ -0,0 +1,73 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 15:38:28 +0200 +Subject: MINOR: h2: pass accept-invalid-http-request down the request parser +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=014945a1508f43e88ac4e89950fa9037e4fb0679 + +We're adding a new argument "relaxed" to h2_make_htx_request() so that +we can control its level of acceptance of certain invalid requests at +the proxy level with "option accept-invalid-http-request". The goal +will be to add deactivable checks that are still desirable to have by +default. For now no test is subject to it. + +(cherry picked from commit d93a00861d714313faa0395ff9e2acb14b0a2fca) + [ad: backported for following fix : BUG/MINOR: h2: reject more chars + from the :path pseudo header] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit b6be1a4f858eb6602490c192235114c1a163fef9) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 26fa3a285df0748fc79e73e552161268b66fb527) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + include/haproxy/h2.h | 2 +- + src/h2.c | 6 +++++- + src/mux_h2.c | 3 ++- + 3 files changed, 8 insertions(+), 3 deletions(-) + +diff --git a/include/haproxy/h2.h b/include/haproxy/h2.h +index 84e4c76fc260..4082b38a80f9 100644 +--- a/include/haproxy/h2.h ++++ b/include/haproxy/h2.h +@@ -207,7 +207,7 @@ extern struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES]; + /* various protocol processing functions */ + + int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len); +-int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len); ++int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed); + int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, char *upgrade_protocol); + int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx); + +diff --git a/src/h2.c b/src/h2.c +index 76c936783461..cf42b7a5610e 100644 +--- a/src/h2.c ++++ b/src/h2.c +@@ -296,8 +296,12 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, + * + * The Cookie header will be reassembled at the end, and for this, the <list> + * will be used to create a linked list, so its contents may be destroyed. ++ * ++ * When <relaxed> is non-nul, some non-dangerous checks will be ignored. This ++ * is in order to satisfy "option accept-invalid-http-request" for ++ * interoperability purposes. + */ +-int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len) ++int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed) + { + struct ist phdr_val[H2_PHDR_NUM_ENTRIES]; + uint32_t fields; /* bit mask of H2_PHDR_FND_* */ +diff --git a/src/mux_h2.c b/src/mux_h2.c +index 752f074cac87..9e5ee8a56c34 100644 +--- a/src/mux_h2.c ++++ b/src/mux_h2.c +@@ -5090,7 +5090,8 @@ static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f + if (h2c->flags & H2_CF_IS_BACK) + outlen = h2_make_htx_response(list, htx, &msgf, body_len, upgrade_protocol); + else +- outlen = h2_make_htx_request(list, htx, &msgf, body_len); ++ outlen = h2_make_htx_request(list, htx, &msgf, body_len, ++ !!(((const struct session *)h2c->conn->owner)->fe->options2 & PR_O2_REQBUG_OK)); + + if (outlen < 0 || htx_free_space(htx) < global.tune.maxrewrite) { + /* too large headers? this is a stream error only */ +-- +2.43.0 + diff --git a/debian/patches/MINOR-http-add-new-function-http_path_has_forbidden_.patch b/debian/patches/MINOR-http-add-new-function-http_path_has_forbidden_.patch new file mode 100644 index 0000000..46cdf99 --- /dev/null +++ b/debian/patches/MINOR-http-add-new-function-http_path_has_forbidden_.patch @@ -0,0 +1,56 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 15:24:54 +0200 +Subject: MINOR: http: add new function http_path_has_forbidden_char() +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=c699bb17b7e334c9d56e829422e29e5a204615ec + +As its name implies, this function checks if a path component has any +forbidden headers starting at the designated location. The goal is to +seek from the result of a successful ist_find_range() for more precise +chars. Here we're focusing on 0x00-0x1F, 0x20 and 0x23 to make sure +we're not too strict at this point. + +(cherry picked from commit 30f58f4217d585efeac3d85cb1b695ba53b7760b) + [ad: backported for following fix : BUG/MINOR: h2: reject more chars + from the :path pseudo header] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit b491940181a88bb6c69ab2afc24b93a50adfa67c) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit f7666e5e43ce63e804ebffdf224d92cfd3367282) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + include/haproxy/http.h | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/include/haproxy/http.h b/include/haproxy/http.h +index 41eca98a1e87..534b6ec2b2f0 100644 +--- a/include/haproxy/http.h ++++ b/include/haproxy/http.h +@@ -190,6 +190,25 @@ static inline int http_header_has_forbidden_char(const struct ist ist, const cha + return 0; + } + ++/* Looks into <ist> for forbidden characters for :path values (0x00..0x1F, ++ * 0x20, 0x23), starting at pointer <start> which must be within <ist>. ++ * Returns non-zero if such a character is found, 0 otherwise. When run on ++ * unlikely header match, it's recommended to first check for the presence ++ * of control chars using ist_find_ctl(). ++ */ ++static inline int http_path_has_forbidden_char(const struct ist ist, const char *start) ++{ ++ do { ++ if ((uint8_t)*start <= 0x23) { ++ if ((uint8_t)*start < 0x20) ++ return 1; ++ if ((1U << ((uint8_t)*start & 0x1F)) & ((1<<3) | (1<<0))) ++ return 1; ++ } ++ start++; ++ } while (start < istend(ist)); ++ return 0; ++} + + #endif /* _HAPROXY_HTTP_H */ + +-- +2.43.0 + diff --git a/debian/patches/MINOR-ist-add-new-function-ist_find_range-to-find-a-.patch b/debian/patches/MINOR-ist-add-new-function-ist_find_range-to-find-a-.patch new file mode 100644 index 0000000..40c3a08 --- /dev/null +++ b/debian/patches/MINOR-ist-add-new-function-ist_find_range-to-find-a-.patch @@ -0,0 +1,84 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 15:23:19 +0200 +Subject: MINOR: ist: add new function ist_find_range() to find a character + range +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=b375df60341c7f7a4904c2d8041a09c66115c754 + +This looks up the character range <min>..<max> in the input string and +returns a pointer to the first one found. It's essentially the equivalent +of ist_find_ctl() in that it searches by 32 or 64 bits at once, but deals +with a range. + +(cherry picked from commit 197668de975e495f0c0f0e4ff51b96203fa9842d) + [ad: backported for following fix : BUG/MINOR: h2: reject more chars + from the :path pseudo header] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 451ac6628acc4b9eed3260501a49c60d4e4d4e55) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 3468f7f8e04c9c5ca5c985c7511e05e78fe1eded) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + include/import/ist.h | 47 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 47 insertions(+) + +diff --git a/include/import/ist.h b/include/import/ist.h +index 978fb3c72247..38fe9363c2a1 100644 +--- a/include/import/ist.h ++++ b/include/import/ist.h +@@ -746,6 +746,53 @@ static inline const char *ist_find_ctl(const struct ist ist) + return NULL; + } + ++/* Returns a pointer to the first character found <ist> that belongs to the ++ * range [min:max] inclusive, or NULL if none is present. The function is ++ * optimized for strings having no such chars by processing up to sizeof(long) ++ * bytes at once on architectures supporting efficient unaligned accesses. ++ * Despite this it is not very fast (~0.43 byte/cycle) and should mostly be ++ * used on low match probability when it can save a call to a much slower ++ * function. Will not work for characters 0x80 and above. It's optimized for ++ * min and max to be known at build time. ++ */ ++static inline const char *ist_find_range(const struct ist ist, unsigned char min, unsigned char max) ++{ ++ const union { unsigned long v; } __attribute__((packed)) *u; ++ const char *curr = (void *)ist.ptr - sizeof(long); ++ const char *last = curr + ist.len; ++ unsigned long l1, l2; ++ ++ /* easier with an exclusive boundary */ ++ max++; ++ ++ do { ++ curr += sizeof(long); ++ if (curr > last) ++ break; ++ u = (void *)curr; ++ /* add 0x<min><min><min><min>..<min> then subtract ++ * 0x<max><max><max><max>..<max> to the value to generate a ++ * carry in the lower byte if the byte contains a lower value. ++ * If we generate a bit 7 that was not there, it means the byte ++ * was min..max. ++ */ ++ l2 = u->v; ++ l1 = ~l2 & ((~0UL / 255) * 0x80); /* 0x808080...80 */ ++ l2 += (~0UL / 255) * min; /* 0x<min><min>..<min> */ ++ l2 -= (~0UL / 255) * max; /* 0x<max><max>..<max> */ ++ } while ((l1 & l2) == 0); ++ ++ last += sizeof(long); ++ if (__builtin_expect(curr < last, 0)) { ++ do { ++ if ((unsigned char)(*curr - min) < (unsigned char)(max - min)) ++ return curr; ++ curr++; ++ } while (curr < last); ++ } ++ return NULL; ++} ++ + /* looks for first occurrence of character <chr> in string <ist> and returns + * the tail of the string starting with this character, or (ist.end,0) if not + * found. +-- +2.43.0 + diff --git a/debian/patches/REGTESTS-http-rules-add-accept-invalid-http-request-.patch b/debian/patches/REGTESTS-http-rules-add-accept-invalid-http-request-.patch new file mode 100644 index 0000000..60caf8f --- /dev/null +++ b/debian/patches/REGTESTS-http-rules-add-accept-invalid-http-request-.patch @@ -0,0 +1,44 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 19:52:45 +0200 +Subject: REGTESTS: http-rules: add accept-invalid-http-request for + normalize-uri tests +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=65849396fd6f192d9f14e81702c6c3851e580345 + +We'll soon block the '#' by default so let's prepare the test to continue +to work. + +(cherry picked from commit 069d0e221e58a46119d7c049bb07fa4bcb8d0075) + [ad: backported for following fix : BUG/MINOR: h2: reject more chars + from the :path pseudo header] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 1660481fab69856a39ac44cf88b76cdbcc0ea954) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 90d0300cea6cda18a4e20369f4dc0b4c4783d6c9) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + reg-tests/http-rules/normalize_uri.vtc | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/reg-tests/http-rules/normalize_uri.vtc b/reg-tests/http-rules/normalize_uri.vtc +index 82c810718df1..34905eaf93ae 100644 +--- a/reg-tests/http-rules/normalize_uri.vtc ++++ b/reg-tests/http-rules/normalize_uri.vtc +@@ -127,6 +127,7 @@ haproxy h1 -conf { + + frontend fe_fragment_strip + bind "fd@${fe_fragment_strip}" ++ option accept-invalid-http-request + + http-request set-var(txn.before) url + http-request normalize-uri fragment-strip +@@ -139,6 +140,7 @@ haproxy h1 -conf { + + frontend fe_fragment_encode + bind "fd@${fe_fragment_encode}" ++ option accept-invalid-http-request + + http-request set-var(txn.before) url + http-request normalize-uri fragment-encode +-- +2.43.0 + diff --git a/debian/patches/REGTESTS-http-rules-verify-that-we-block-by-default-.patch b/debian/patches/REGTESTS-http-rules-verify-that-we-block-by-default-.patch new file mode 100644 index 0000000..6703482 --- /dev/null +++ b/debian/patches/REGTESTS-http-rules-verify-that-we-block-by-default-.patch @@ -0,0 +1,50 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 19:53:51 +0200 +Subject: REGTESTS: http-rules: verify that we block '#' by default for + normalize-uri +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=b6b330eb117d520a890e5b3cd623eaa73479db1b + +Since we now block fragments by default, let's add an extra test there +to confirm that it's blocked even when stripping it. + +(cherry picked from commit 4d0175b54b2b4eeb01aa6e31282b0a5b0d7d8ace) + [ad: backported to test conformance of BUG/MINOR: h1: do not accept '#' + as part of the URI component] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit b3f26043df74c661155566a0abd56103e8116078) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 41d161ccbbfa846b4b17ed0166ff08f6bf0c3ea1) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + reg-tests/http-rules/normalize_uri.vtc | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/reg-tests/http-rules/normalize_uri.vtc b/reg-tests/http-rules/normalize_uri.vtc +index 34905eaf93ae..ad7b44acfe55 100644 +--- a/reg-tests/http-rules/normalize_uri.vtc ++++ b/reg-tests/http-rules/normalize_uri.vtc +@@ -151,6 +151,11 @@ haproxy h1 -conf { + + default_backend be + ++ frontend fe_fragment_block ++ bind "fd@${fe_fragment_block}" ++ http-request normalize-uri fragment-strip ++ default_backend be ++ + backend be + server s1 ${s1_addr}:${s1_port} + +@@ -536,3 +541,9 @@ client c10 -connect ${h1_fe_fragment_encode_sock} { + expect resp.http.before == "*" + expect resp.http.after == "*" + } -run ++ ++client c11 -connect ${h1_fe_fragment_block_sock} { ++ txreq -url "/#foo" ++ rxresp ++ expect resp.status == 400 ++} -run +-- +2.43.0 + diff --git a/debian/patches/REORG-http-move-has_forbidden_char-from-h2.c-to-http.patch b/debian/patches/REORG-http-move-has_forbidden_char-from-h2.c-to-http.patch new file mode 100644 index 0000000..5bf1eef --- /dev/null +++ b/debian/patches/REORG-http-move-has_forbidden_char-from-h2.c-to-http.patch @@ -0,0 +1,109 @@ +From: Willy Tarreau <w@1wt.eu> +Date: Tue, 8 Aug 2023 17:00:50 +0200 +Subject: REORG: http: move has_forbidden_char() from h2.c to http.h +Origin: https://git.haproxy.org/?p=haproxy-2.6.git;a=commit;h=4a776fd01560a8dfa7a57b30b4d5249c8da7b12c + +This function is not H2 specific but rather generic to HTTP. We'll +need it in H3 soon, so let's move it to HTTP and rename it to +http_header_has_forbidden_char(). + +(cherry picked from commit d4069f3cee0f6e94afaec518b6373dd368073f52) + [ad: backported for next patch BUG/MAJOR: h3: reject header values + containing invalid chars] +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 21c4ffd025115058994a3e2765c17fc3cee52f90) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +(cherry picked from commit 9c0bc4f201cf58c10706416cb4807c0f4794f8ac) +Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com> +--- + include/haproxy/http.h | 18 ++++++++++++++++++ + src/h2.c | 23 +++-------------------- + 2 files changed, 21 insertions(+), 20 deletions(-) + +diff --git a/include/haproxy/http.h b/include/haproxy/http.h +index f597ee4cd1dc..41eca98a1e87 100644 +--- a/include/haproxy/http.h ++++ b/include/haproxy/http.h +@@ -173,6 +173,24 @@ static inline struct http_uri_parser http_uri_parser_init(const struct ist uri) + return parser; + } + ++/* Looks into <ist> for forbidden characters for header values (0x00, 0x0A, ++ * 0x0D), starting at pointer <start> which must be within <ist>. Returns ++ * non-zero if such a character is found, 0 otherwise. When run on unlikely ++ * header match, it's recommended to first check for the presence of control ++ * chars using ist_find_ctl(). ++ */ ++static inline int http_header_has_forbidden_char(const struct ist ist, const char *start) ++{ ++ do { ++ if ((uint8_t)*start <= 0x0d && ++ (1U << (uint8_t)*start) & ((1<<13) | (1<<10) | (1<<0))) ++ return 1; ++ start++; ++ } while (start < istend(ist)); ++ return 0; ++} ++ ++ + #endif /* _HAPROXY_HTTP_H */ + + /* +diff --git a/src/h2.c b/src/h2.c +index f794262ee7af..76c936783461 100644 +--- a/src/h2.c ++++ b/src/h2.c +@@ -49,23 +49,6 @@ struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES] = { + [H2_FT_CONTINUATION ] = { .dir = 3, .min_id = 1, .max_id = H2_MAX_STREAM_ID, .min_len = 0, .max_len = H2_MAX_FRAME_LEN, }, + }; + +-/* Looks into <ist> for forbidden characters for header values (0x00, 0x0A, +- * 0x0D), starting at pointer <start> which must be within <ist>. Returns +- * non-zero if such a character is found, 0 otherwise. When run on unlikely +- * header match, it's recommended to first check for the presence of control +- * chars using ist_find_ctl(). +- */ +-static int has_forbidden_char(const struct ist ist, const char *start) +-{ +- do { +- if ((uint8_t)*start <= 0x0d && +- (1U << (uint8_t)*start) & ((1<<13) | (1<<10) | (1<<0))) +- return 1; +- start++; +- } while (start < istend(ist)); +- return 0; +-} +- + /* Prepare the request line into <htx> from pseudo headers stored in <phdr[]>. + * <fields> indicates what was found so far. This should be called once at the + * detection of the first general header field or at the end of the request if +@@ -353,7 +336,7 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms + * rejecting NUL, CR and LF characters. + */ + ctl = ist_find_ctl(list[idx].v); +- if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) ++ if (unlikely(ctl) && http_header_has_forbidden_char(list[idx].v, ctl)) + goto fail; + + if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) { +@@ -638,7 +621,7 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m + * rejecting NUL, CR and LF characters. + */ + ctl = ist_find_ctl(list[idx].v); +- if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) ++ if (unlikely(ctl) && http_header_has_forbidden_char(list[idx].v, ctl)) + goto fail; + + if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) { +@@ -797,7 +780,7 @@ int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx) + * rejecting NUL, CR and LF characters. + */ + ctl = ist_find_ctl(list[idx].v); +- if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl)) ++ if (unlikely(ctl) && http_header_has_forbidden_char(list[idx].v, ctl)) + goto fail; + + if (!htx_add_trailer(htx, list[idx].n, list[idx].v)) +-- +2.43.0 + diff --git a/debian/patches/debianize-dconv.patch b/debian/patches/debianize-dconv.patch new file mode 100644 index 0000000..34710ce --- /dev/null +++ b/debian/patches/debianize-dconv.patch @@ -0,0 +1,170 @@ +From: Apollon Oikonomopoulos <apoikos@debian.org> +Date: Wed, 29 Apr 2015 13:51:49 +0300 +Subject: [PATCH] dconv: debianize + + - Use Debian bootstrap and jquery packages + - Add Debian-related resources to the template + - Use the package's version instead of HAProxy's git version + - Strip the conversion date from the output to ensure reproducible + build. + - 2020-01-17: make get_haproxy_debian_version() return a string, for py3 + compatibility + +diff --git a/debian/dconv/haproxy-dconv.py b/debian/dconv/haproxy-dconv.py +index fe2b96dce325..702eefac6a3b 100755 +--- a/debian/dconv/haproxy-dconv.py ++++ b/debian/dconv/haproxy-dconv.py +@@ -44,12 +44,11 @@ VERSION = "" + HAPROXY_GIT_VERSION = False + + def main(): +- global VERSION, HAPROXY_GIT_VERSION ++ global HAPROXY_GIT_VERSION + + usage="Usage: %prog --infile <infile> --outfile <outfile>" + + optparser = OptionParser(description='Generate HTML Document from HAProxy configuation.txt', +- version=VERSION, + usage=usage) + optparser.add_option('--infile', '-i', help='Input file mostly the configuration.txt') + optparser.add_option('--outfile','-o', help='Output file') +@@ -65,11 +64,7 @@ def main(): + + os.chdir(os.path.dirname(__file__)) + +- VERSION = get_git_version() +- if not VERSION: +- sys.exit(1) +- +- HAPROXY_GIT_VERSION = get_haproxy_git_version(os.path.dirname(option.infile)) ++ HAPROXY_GIT_VERSION = get_haproxy_debian_version(os.path.dirname(option.infile)) + + convert(option.infile, option.outfile, option.base) + +@@ -114,6 +109,15 @@ def get_haproxy_git_version(path): + version = re.sub(r'-g.*', '', version) + return version + ++def get_haproxy_debian_version(path): ++ try: ++ version = subprocess.check_output(["dpkg-parsechangelog", "-Sversion"], ++ cwd=os.path.join(path, "..")) ++ except subprocess.CalledProcessError: ++ return False ++ ++ return version.decode("utf-8").strip() ++ + def getTitleDetails(string): + array = string.split(".") + +@@ -506,7 +510,6 @@ def convert(infile, outfile, base=''): + keywords = keywords, + keywordsCount = keywordsCount, + keyword_conflicts = keyword_conflicts, +- version = VERSION, + date = datetime.datetime.now().strftime("%Y/%m/%d"), + ) + except TopLevelLookupException: +@@ -524,7 +527,6 @@ def convert(infile, outfile, base=''): + keywords = keywords, + keywordsCount = keywordsCount, + keyword_conflicts = keyword_conflicts, +- version = VERSION, + date = datetime.datetime.now().strftime("%Y/%m/%d"), + footer = footer + ) +diff --git a/debian/dconv/templates/template.html b/debian/dconv/templates/template.html +index c72b3558c2dd..9aefa16dd82d 100644 +--- a/debian/dconv/templates/template.html ++++ b/debian/dconv/templates/template.html +@@ -3,8 +3,8 @@ + <head> + <meta charset="utf-8" /> + <title>${headers['title']} ${headers['version']} - ${headers['subtitle']}</title> +- <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" /> +- <link href="${base}css/page.css?${version}" rel="stylesheet" /> ++ <link href="${base}css/bootstrap.min.css" rel="stylesheet" /> ++ <link href="${base}css/page.css" rel="stylesheet" /> + </head> + <body> + <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> +@@ -15,7 +15,7 @@ + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> +- <a class="navbar-brand" href="${base}index.html">${headers['title']} <small>${headers['subtitle']}</small></a> ++ <a class="navbar-brand" href="${base}configuration.html">${headers['title']}</a> + </div> + <!-- /.navbar-header --> + +@@ -24,31 +24,16 @@ + <ul class="nav navbar-nav"> + <li><a href="http://www.haproxy.org/">HAProxy home page</a></li> + <li class="dropdown"> +- <a href="#" class="dropdown-toggle" data-toggle="dropdown">Versions <b class="caret"></b></a> ++ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Debian resources <b class="caret"></b></a> + <ul class="dropdown-menu"> + ## TODO : provide a structure to dynamically generate per version links +- <li class="dropdown-header">HAProxy 1.4</li> +- <li><a href="${base}configuration-1.4.html">Configuration Manual <small>(stable)</small></a></li> +- <li><a href="${base}snapshot/configuration-1.4.html">Configuration Manual <small>(snapshot)</small></a></li> +- <li><a href="http://git.1wt.eu/git/haproxy-1.4.git/">GIT Repository</a></li> +- <li><a href="http://www.haproxy.org/git/?p=haproxy-1.4.git">Browse repository</a></li> +- <li><a href="http://www.haproxy.org/download/1.4/">Browse directory</a></li> +- <li class="divider"></li> +- <li class="dropdown-header">HAProxy 1.5</li> +- <li><a href="${base}configuration-1.5.html">Configuration Manual <small>(stable)</small></a></li> +- <li><a href="${base}snapshot/configuration-1.5.html">Configuration Manual <small>(snapshot)</small></a></li> +- <li><a href="http://git.1wt.eu/git/haproxy-1.5.git/">GIT Repository</a></li> +- <li><a href="http://www.haproxy.org/git/?p=haproxy-1.5.git">Browse repository</a></li> +- <li><a href="http://www.haproxy.org/download/1.5/">Browse directory</a></li> +- <li class="divider"></li> +- <li class="dropdown-header">HAProxy 1.6</li> +- <li><a href="${base}configuration-1.6.html">Configuration Manual <small>(stable)</small></a></li> +- <li><a href="${base}snapshot/configuration-1.6.html">Configuration Manual <small>(snapshot)</small></a></li> +- <li><a href="${base}intro-1.6.html">Starter Guide <small>(stable)</small></a></li> +- <li><a href="${base}snapshot/intro-1.6.html">Starter Guide <small>(snapshot)</small></a></li> +- <li><a href="http://git.1wt.eu/git/haproxy.git/">GIT Repository</a></li> +- <li><a href="http://www.haproxy.org/git/?p=haproxy.git">Browse repository</a></li> +- <li><a href="http://www.haproxy.org/download/1.6/">Browse directory</a></li> ++ <li><a href="https://bugs.debian.org/src:haproxy">Bug Tracking System</a></li> ++ <li><a href="https://packages.debian.org/haproxy">Package page</a></li> ++ <li><a href="http://tracker.debian.org/pkg/haproxy">Package Tracking System</a></li> ++ <li class="divider"></li> ++ <li><a href="${base}intro.html">Starter Guide</a></li> ++ <li><a href="${base}configuration.html">Configuration Manual</a></li> ++ <li><a href="http://anonscm.debian.org/gitweb/?p=pkg-haproxy/haproxy.git">Package Git Repository</a></li> + </ul> + </li> + </ul> +@@ -72,7 +57,7 @@ + The feature is automatically disabled when the search field is focused. + </p> + <p class="text-right"> +- <small>Converted with <a href="https://github.com/cbonte/haproxy-dconv">haproxy-dconv</a> v<b>${version}</b> on <b>${date}</b></small> ++ <small>Converted with <a href="https://github.com/cbonte/haproxy-dconv">haproxy-dconv</a></small> + </p> + </div> + <!-- /.sidebar --> +@@ -83,7 +68,7 @@ + <div class="text-center"> + <h1>${headers['title']}</h1> + <h2>${headers['subtitle']}</h2> +- <p><strong>${headers['version']}</strong></p> ++ <p><strong>${headers['version']} (Debian)</strong></p> + <p> + <a href="http://www.haproxy.org/" title="HAProxy Home Page"><img src="${base}img/logo-med.png" /></a><br> + ${headers['author']}<br> +@@ -114,9 +99,9 @@ + </div> + <!-- /#wrapper --> + +- <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> +- <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/js/bootstrap.min.js"></script> +- <script src="//cdnjs.cloudflare.com/ajax/libs/typeahead.js/0.11.1/typeahead.bundle.min.js"></script> ++ <script src="${base}js/jquery.min.js"></script> ++ <script src="${base}js/bootstrap.min.js"></script> ++ <script src="${base}js/typeahead.bundle.js"></script> + <script> + /* Keyword search */ + var searchFocus = false diff --git a/debian/patches/haproxy.service-add-documentation.patch b/debian/patches/haproxy.service-add-documentation.patch new file mode 100644 index 0000000..a60b0d1 --- /dev/null +++ b/debian/patches/haproxy.service-add-documentation.patch @@ -0,0 +1,23 @@ +From: Debian HAProxy Maintainers + <pkg-haproxy-maintainers@lists.alioth.debian.org> +Date: Sun, 25 Mar 2018 11:31:50 +0200 +Subject: Add documentation field to the systemd unit + +Forwarded: no +Last-Update: 2014-01-03 +--- + admin/systemd/haproxy.service.in | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/admin/systemd/haproxy.service.in b/admin/systemd/haproxy.service.in +index 243acf2..ac88c37 100644 +--- a/admin/systemd/haproxy.service.in ++++ b/admin/systemd/haproxy.service.in +@@ -1,5 +1,7 @@ + [Unit] + Description=HAProxy Load Balancer ++Documentation=man:haproxy(1) ++Documentation=file:/usr/share/doc/haproxy/configuration.txt.gz + After=network-online.target rsyslog.service + Wants=network-online.target + diff --git a/debian/patches/haproxy.service-make-systemd-bind-dev-log-inside-chroot.patch b/debian/patches/haproxy.service-make-systemd-bind-dev-log-inside-chroot.patch new file mode 100644 index 0000000..666f916 --- /dev/null +++ b/debian/patches/haproxy.service-make-systemd-bind-dev-log-inside-chroot.patch @@ -0,0 +1,21 @@ +From: Vincent Bernat <bernat@debian.org> +Date: Thu, 25 Nov 2021 21:35:48 +0100 +Subject: haproxy.service: make systemd bind /dev/log inside chroot + +This enables logging to work without rsyslog being present. +--- + admin/systemd/haproxy.service.in | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/admin/systemd/haproxy.service.in b/admin/systemd/haproxy.service.in +index 0288568..20824df 100644 +--- a/admin/systemd/haproxy.service.in ++++ b/admin/systemd/haproxy.service.in +@@ -8,6 +8,7 @@ Wants=network-online.target + [Service] + EnvironmentFile=-/etc/default/haproxy + EnvironmentFile=-/etc/sysconfig/haproxy ++BindReadOnlyPaths=/dev/log:/var/lib/haproxy/dev/log + Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock" + ExecStart=@SBINDIR@/haproxy -Ws -f $CONFIG -p $PIDFILE $EXTRAOPTS + ExecReload=@SBINDIR@/haproxy -Ws -f $CONFIG -c -q $EXTRAOPTS diff --git a/debian/patches/haproxy.service-start-after-syslog.patch b/debian/patches/haproxy.service-start-after-syslog.patch new file mode 100644 index 0000000..14577bd --- /dev/null +++ b/debian/patches/haproxy.service-start-after-syslog.patch @@ -0,0 +1,27 @@ +From: Apollon Oikonomopoulos <apoikos@debian.org> +Date: Sun, 25 Mar 2018 11:31:50 +0200 +Subject: Start after rsyslog.service + +As HAProxy is running chrooted by default, we rely on an additional syslog +socket created by rsyslog inside the chroot for logging. As this socket cannot +trigger syslog activation, we explicitly order HAProxy after rsyslog.service. +Note that we are not using syslog.service here, since the additional socket is +rsyslog-specific. +Forwarded: no +Last-Update: 2017-12-01 +--- + admin/systemd/haproxy.service.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/admin/systemd/haproxy.service.in b/admin/systemd/haproxy.service.in +index 74e66e3..243acf2 100644 +--- a/admin/systemd/haproxy.service.in ++++ b/admin/systemd/haproxy.service.in +@@ -1,6 +1,6 @@ + [Unit] + Description=HAProxy Load Balancer +-After=network-online.target ++After=network-online.target rsyslog.service + Wants=network-online.target + + [Service] diff --git a/debian/patches/reproducible.patch b/debian/patches/reproducible.patch new file mode 100644 index 0000000..bbc95b8 --- /dev/null +++ b/debian/patches/reproducible.patch @@ -0,0 +1,13 @@ +diff --git a/Makefile b/Makefile +index 566bdb26a3e7..8603dea25c21 100644 +--- a/Makefile ++++ b/Makefile +@@ -975,7 +975,7 @@ src/haproxy.o: src/haproxy.c $(DEP) + -DBUILD_ARCH='"$(strip $(ARCH))"' \ + -DBUILD_CPU='"$(strip $(CPU))"' \ + -DBUILD_CC='"$(strip $(CC))"' \ +- -DBUILD_CFLAGS='"$(strip $(VERBOSE_CFLAGS))"' \ ++ -DBUILD_CFLAGS='"$(filter-out -ffile-prefix-map=%,$(strip $(VERBOSE_CFLAGS)))"' \ + -DBUILD_OPTIONS='"$(strip $(BUILD_OPTIONS))"' \ + -DBUILD_DEBUG='"$(strip $(DEBUG))"' \ + -DBUILD_FEATURES='"$(strip $(BUILD_FEATURES))"' \ diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..7136a90 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,19 @@ +haproxy.service-start-after-syslog.patch +haproxy.service-add-documentation.patch +haproxy.service-make-systemd-bind-dev-log-inside-chroot.patch +reproducible.patch +REORG-http-move-has_forbidden_char-from-h2.c-to-http.patch +BUG-MAJOR-h3-reject-header-values-containing-invalid.patch +BUG-MAJOR-http-reject-any-empty-content-length-heade.patch +MINOR-ist-add-new-function-ist_find_range-to-find-a-.patch +MINOR-http-add-new-function-http_path_has_forbidden_.patch +MINOR-h2-pass-accept-invalid-http-request-down-the-r.patch +REGTESTS-http-rules-add-accept-invalid-http-request-.patch +BUG-MINOR-h1-do-not-accept-as-part-of-the-URI-compon.patch +BUG-MINOR-h2-reject-more-chars-from-the-path-pseudo-.patch +BUG-MINOR-h3-reject-more-chars-from-the-path-pseudo-.patch +REGTESTS-http-rules-verify-that-we-block-by-default-.patch +DOC-clarify-the-handling-of-URL-fragments-in-request.patch + +# applied during the build process: +# debianize-dconv.patch |