Merge ~athos/ubuntu/+source/haproxy:MRE-focal into ubuntu/+source/haproxy:ubuntu/focal-devel

Proposed by Athos Ribeiro
Status: Merged
Approved by: git-ubuntu bot
Approved revision: not available
Merged at revision: 43025670639764462f3d8e1de02bccfe3c3b685a
Proposed branch: ~athos/ubuntu/+source/haproxy:MRE-focal
Merge into: ubuntu/+source/haproxy:ubuntu/focal-devel
Diff against target: 2349 lines (+636/-305)
45 files modified
.github/workflows/cross-zoo.yml (+1/-1)
.github/workflows/vtest.yml (+1/-0)
.gitignore (+1/-0)
CHANGELOG (+61/-0)
SUBVERS (+1/-1)
VERDATE (+2/-2)
VERSION (+1/-1)
debian/changelog (+19/-0)
debian/patches/series (+0/-2)
dev/null (+0/-150)
doc/configuration.txt (+37/-14)
include/common/h2.h (+2/-2)
include/common/ist.h (+53/-0)
include/common/time.h (+2/-1)
include/types/spoe.h (+1/-0)
reg-tests/http-messaging/h1_to_h1.vtc (+26/-0)
reg-tests/http-messaging/h2_to_h1.vtc (+60/-0)
reg-tests/http-rules/fragment_in_uri.vtc (+35/-0)
reg-tests/http-rules/h1or2_to_h1c.vtc (+12/-4)
scripts/publish-release (+3/-0)
src/checks.c (+10/-1)
src/chunk.c (+7/-3)
src/debug.c (+3/-2)
src/filters.c (+2/-3)
src/flt_spoe.c (+21/-9)
src/h1.c (+36/-7)
src/h2.c (+76/-13)
src/haproxy.c (+18/-1)
src/hlua.c (+17/-3)
src/http.c (+1/-1)
src/http_msg.c (+10/-3)
src/log.c (+4/-0)
src/mux_h1.c (+15/-3)
src/mux_h2.c (+15/-5)
src/mworker.c (+21/-5)
src/namespace.c (+1/-0)
src/proto_htx.c (+2/-1)
src/proto_tcp.c (+3/-1)
src/proxy.c (+2/-2)
src/sample.c (+2/-2)
src/server.c (+31/-58)
src/ssl_sock.c (+1/-1)
src/stream_interface.c (+16/-0)
src/tcp_rules.c (+2/-2)
src/time.c (+2/-1)
Reviewer Review Type Date Requested Status
git-ubuntu bot Approve
Sergio Durigan Junior (community) Approve
Canonical Server Reporter Pending
Review via email: mp+454922@code.launchpad.net

Description of the change

HAProxy MRE introducing the newest point release version for focal.

All relevant information is available at the MRE bug in LP: #2028418.

PPA: https://launchpad.net/~athos-ribeiro/+archive/ubuntu/haproxy-mre/+packages

DEP8 test results:

  - haproxy/2.0.33-0ubuntu0.1
    + ✅ haproxy on focal for amd64 @ 31.10.23 23:06:37 Log️ 🗒️
    + ✅ haproxy on focal for arm64 @ 31.10.23 23:03:26 Log️ 🗒️
    + ✅ haproxy on focal for armhf @ 31.10.23 23:02:18 Log️ 🗒️
    + ✅ haproxy on focal for ppc64el @ 31.10.23 22:59:11 Log️ 🗒️
    + ✅ haproxy on focal for s390x @ 31.10.23 23:00:08 Log️ 🗒️

To post a comment you must log in.
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thank you for the MP, Athos.

Also, thank you very much for the extremely well written MRE bug! It has made reviewing the changes much easier :-).

I agree with your analysis of the situation there. One of the major fixes being introduced here and in the Jammy MRE ("http: reject any empty content-length header value") is actually already part of the haproxy package we ship our users, because it's been fixed by the security team. Therefore, we're only left with one new major fix being backported.

Anyway, the changes LGTM. dep8 tests are passing (although they're somewhat superficial, as explained in the MRE document). I'm relying on the Microsoft Github's actions to have a better picture of the testing being done here. As I mentioned to you in the channel, I believe it may be worth patching the repository to make the "Spec Compliance" test run. It seems to be an important test and I'd like to see its results.

Otherwise, there's not much to say here. I also agree with leaving Lunar out of this MRE round.

I'm reviewing this MP as Needs Information just because of the "Spec Compliance" test. Let me know what you think.

Thanks.

review: Needs Information
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Athos was kind enough to patch the git repo and rerun the "Spec Compliance" test, which passed:

https://github.com/athos-ribeiro/haproxy-2.4/actions/runs/6962364281

Therefore, I'm approving this MP. Thanks again!

review: Approve
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Ops, Athos correctly told me that the "Spec Compliance" test only applies to haproxy-2.4, which will be MRE'd to Jammy. This means that the results I posted above do not apply to this MP. That doesn't change my approval, though.

Revision history for this message
git-ubuntu bot (git-ubuntu-bot) wrote :

Approvers: athos-ribeiro, sergiodj
Uploaders: athos-ribeiro, sergiodj
MP auto-approved

review: Approve
Revision history for this message
Athos Ribeiro (athos) wrote :

Thanks, Sergio!

Uploaded!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.github/workflows/cross-zoo.yml b/.github/workflows/cross-zoo.yml
2index e2a5816..f2c8d7a 100644
3--- a/.github/workflows/cross-zoo.yml
4+++ b/.github/workflows/cross-zoo.yml
5@@ -97,7 +97,7 @@ jobs:
6 sudo apt-get -yq --force-yes install \
7 gcc-${{ matrix.platform.arch }} \
8 ${{ matrix.platform.libs }}
9- - uses: actions/checkout@v2
10+ - uses: actions/checkout@v3
11
12
13 - name: install quictls
14diff --git a/.github/workflows/vtest.yml b/.github/workflows/vtest.yml
15index b919418..a12e4e1 100644
16--- a/.github/workflows/vtest.yml
17+++ b/.github/workflows/vtest.yml
18@@ -147,3 +147,4 @@ jobs:
19 sudo cat $asan
20 echo "::endgroup::"
21 done
22+ exit 1
23diff --git a/.gitignore b/.gitignore
24index 3a760af..e5d132e 100644
25--- a/.gitignore
26+++ b/.gitignore
27@@ -35,6 +35,7 @@
28 *.rej
29 *.orig
30 *.bak
31+*.sw[op]
32 # And reject some specific files
33 /contrib/base64/base64rev
34 /contrib/halog/halog
35diff --git a/CHANGELOG b/CHANGELOG
36index 4b5713f..305eea6 100644
37--- a/CHANGELOG
38+++ b/CHANGELOG
39@@ -1,6 +1,67 @@
40 ChangeLog :
41 ===========
42
43+2023/08/19 : 2.0.33
44+ - BUG/MINOR: cfgparse-tcp: leak when re-declaring interface from bind line
45+ - BUG/MINOR: proxy: add missing interface bind free in free_proxy
46+ - BUG/MINOR: server: inherit from netns in srv_settings_cpy()
47+ - BUG/MINOR: namespace: missing free in netns_sig_stop()
48+ - BUG/MEDIUM: mworker: increase maxsock with each new worker
49+ - DOC: Add tune.h2.max-frame-size option to table of contents
50+ - BUG/MINOR: sample: Fix wrong overflow detection in add/sub conveters
51+ - BUG/MINOR: http: Return the right reason for 302
52+ - CI: explicitely highlight VTest result section if there's something
53+ - DOC: configuration: describe Td in Timing events
54+ - BUG/MINOR: chunk: fix chunk_appendf() to not write a zero if buffer is full
55+ - BUG/MAJOR: http-ana: Get a fresh trash buffer for each header value replacement
56+ - BUG/MAJOR: http: reject any empty content-length header value
57+ - MINOR: ist: add new function ist_find_range() to find a character range
58+ - MINOR: ist: Add istend() function to return a pointer to the end of the string
59+ - MINOR: http: add new function http_path_has_forbidden_char()
60+ - MINOR: h2: pass accept-invalid-http-request down the request parser
61+ - BUG/MINOR: h1: do not accept '#' as part of the URI component
62+ - BUG/MINOR: h2: reject more chars from the :path pseudo header
63+ - REGTESTS: http-rules: verify that we block '#' by default for normalize-uri
64+ - DOC: clarify the handling of URL fragments in requests
65+ - BUG/MINOR: http: skip leading zeroes in content-length values
66+
67+2023/06/12 : 2.0.32
68+ - BUG/MEDIUM: mworker: don't register mworker_accept_wrapper() when master FD is wrong
69+ - BUG/MINOR: mworker: prevent incorrect values in uptime
70+ - BUG/MINOR: ssl: Use 'date' instead of 'now' in ocsp stapling callback
71+ - BUG/MINOR: mux-h2: make sure the h2c task exists before refreshing it
72+ - BUG/MEDIUM: spoe: Don't set the default traget for the SPOE agent frontend
73+ - BUG/MEDIUM: mux-h2: erase h2c->wait_event.tasklet on error path
74+ - BUG/MEDIUM: mux-h1: Wakeup H1C on shutw if there is no I/O subscription
75+ - MINOR: proxy/pool: prevent unnecessary calls to pool_gc()
76+ - CI: bump "actions/checkout" to v3 for cross zoo matrix
77+ - BUG/MEDIUM: Update read expiration date on synchronous send
78+ - BUG/MINOR: mux-h2: make sure to produce a log on invalid requests
79+ - MINOR: checks: make sure spread-checks is used also at boot time
80+ - MINOR: clock: measure the total boot time
81+ - BUG/MINOR: checks: postpone the startup of health checks by the boot time
82+ - BUG/MEDIUM: mux-h1: Handle connection error after a synchronous send
83+ - BUG/MINOR: mux-h1: Account consumed output data on synchronous connection error
84+ - BUG/MEDIUM: mux-h1: do not refrain from signaling errors after end of input
85+ - BUG/MINOR: tcp-rules: Don't shortened the inspect-delay when EOI is set
86+ - DOC: config: Clarify conditions to shorten the inspect-delay for TCP rules
87+ - BUG/MINOR: hlua: unsafe hlua_lua2smp() usage
88+ - SCRIPTS: publish-release: update the umask to keep group write access
89+ - BUG/MINOR: log: fix memory error handling in parse_logsrv()
90+ - BUG/MINOR: proxy: missing free in free_proxy for redirect rules
91+ - MINOR: spoe: Don't stop disabled proxies
92+ - BUG/MEDIUM: filters: Don't deinit filters for disabled proxies during startup
93+ - BUG/MINOR: debug: do not emit empty lines in thread dumps
94+ - BUG/MEDIUM: spoe: Don't start new applet if there are enough idle ones
95+ - BUG/MINOR: server: incorrect report for tracking servers leaving drain
96+ - MINOR: server: explicitly commit state change in srv_update_status()
97+ - BUG/MINOR: server: don't miss proxy stats update on server state transitions
98+ - BUG/MINOR: server: don't miss server stats update on server state transitions
99+ - BUG/MINOR: server: don't use date when restoring last_change from state file
100+ - DOC: config: Fix bind/server/peer documentation in the peers section
101+ - CONTRIB: Add vi file extensions to .gitignore
102+ - BUG/MINOR: spoe: Only skip sending new frame after a receive attempt
103+
104 2023/02/14 : 2.0.31
105 - SCRIPTS: announce-release: add a link to the data plane API
106 - CI: github: change "ubuntu-latest" to "ubuntu-20.04"
107diff --git a/SUBVERS b/SUBVERS
108index e1824da..a32020b 100644
109--- a/SUBVERS
110+++ b/SUBVERS
111@@ -1,2 +1,2 @@
112--c8b1c15
113+-83002d4
114
115diff --git a/VERDATE b/VERDATE
116index 2f36592..cb54148 100644
117--- a/VERDATE
118+++ b/VERDATE
119@@ -1,2 +1,2 @@
120-2023-02-14 16:58:28 +0100
121-2023/02/14
122+2023-08-19 11:32:22 +0200
123+2023/08/19
124diff --git a/VERSION b/VERSION
125index e235558..520d2d2 100644
126--- a/VERSION
127+++ b/VERSION
128@@ -1 +1 @@
129-2.0.31
130+2.0.33
131diff --git a/debian/changelog b/debian/changelog
132index 1d940f1..8866696 100644
133--- a/debian/changelog
134+++ b/debian/changelog
135@@ -1,3 +1,22 @@
136+haproxy (2.0.33-0ubuntu0.1) focal; urgency=medium
137+
138+ * New upstream release (LP: #2028418)
139+ - Major and critical bug fixes according to the upstream changelog:
140+ + BUG/MAJOR: http-ana: Get a fresh trash buffer for each header value
141+ replacement
142+ + BUG/MAJOR: http: reject any empty content-length header value
143+ - For further information, refer to the upstream changelog at
144+ https://www.haproxy.org/download/2.0/src/CHANGELOG and to the upstream
145+ release announcements at
146+ https://www.mail-archive.com/haproxy@formilux.org/msg43668.html
147+ (2.0.32), and
148+ https://www.mail-archive.com/haproxy@formilux.org/msg43904.html (2.0.33)
149+ - Remove patches applied by upstream in debian/patches:
150+ + CVE-2023-40225-1.patch
151+ + CVE-2023-40225-2.patch
152+
153+ -- Athos Ribeiro <athos.ribeiro@canonical.com> Tue, 31 Oct 2023 16:00:44 -0300
154+
155 haproxy (2.0.31-0ubuntu0.2) focal-security; urgency=medium
156
157 * SECURITY UPDATE: incorrect handling of empty content-length header
158diff --git a/debian/patches/CVE-2023-40225-1.patch b/debian/patches/CVE-2023-40225-1.patch
159deleted file mode 100644
160index dd454a4..0000000
161--- a/debian/patches/CVE-2023-40225-1.patch
162+++ /dev/null
163@@ -1,284 +0,0 @@
164-From 4b400110a58da1aa58d58d6371875a3a64c0cff6 Mon Sep 17 00:00:00 2001
165-From: Willy Tarreau <w@1wt.eu>
166-Date: Wed, 9 Aug 2023 08:32:48 +0200
167-Subject: [PATCH] BUG/MAJOR: http: reject any empty content-length header
168- value
169-
170-The content-length header parser has its dedicated function, in order
171-to take extreme care about invalid, unparsable, or conflicting values.
172-But there's a corner case in it, by which it stops comparing values
173-when reaching the end of the header. This has for a side effect that
174-an empty value or a value that ends with a comma does not deserve
175-further analysis, and it acts as if the header was absent.
176-
177-While this is not necessarily a problem for the value ending with a
178-comma as it will be cause a header folding and will disappear, it is a
179-problem for the first isolated empty header because this one will not
180-be recontructed when next ones are seen, and will be passed as-is to the
181-backend server. A vulnerable HTTP/1 server hosted behind haproxy that
182-would just use this first value as "0" and ignore the valid one would
183-then not be protected by haproxy and could be attacked this way, taking
184-the payload for an extra request.
185-
186-In field the risk depends on the server. Most commonly used servers
187-already have safe content-length parsers, but users relying on haproxy
188-to protect a known-vulnerable server might be at risk (and the risk of
189-a bug even in a reputable server should never be dismissed).
190-
191-A configuration-based work-around consists in adding the following rule
192-in the frontend, to explicitly reject requests featuring an empty
193-content-length header that would have not be folded into an existing
194-one:
195-
196- http-request deny if { hdr_len(content-length) 0 }
197-
198-The real fix consists in adjusting the parser so that it always expects a
199-value at the beginning of the header or after a comma. It will now reject
200-requests and responses having empty values anywhere in the C-L header.
201-
202-This needs to be backported to all supported versions. Note that the
203-modification was made to functions h1_parse_cont_len_header() and
204-http_parse_cont_len_header(). Prior to 2.8 the latter was in
205-h2_parse_cont_len_header(). One day the two should be refused but the
206-former is also used by Lua.
207-
208-The HTTP messaging reg-tests were completed to test these cases.
209-
210-Thanks to Ben Kallus of Dartmouth College and Narf Industries for
211-reporting this! (this is in GH #2237).
212-
213-(cherry picked from commit 6492f1f29d738457ea9f382aca54537f35f9d856)
214-Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
215-(cherry picked from commit a32f99f6f991d123ea3e307bf8aa63220836d365)
216-Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
217-(cherry picked from commit 65921ee12d88e9fb1fa9f6cd8198fd64b3a3f37f)
218-Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
219-(cherry picked from commit d17c50010d591d1c070e1cb0567a06032d8869e9)
220-[wt: applied to h2_parse_cont_len_header() in src/h2.c instead]
221-Signed-off-by: Willy Tarreau <w@1wt.eu>
222-(cherry picked from commit ba9afd2774c03e434165475b537d0462801f49bb)
223-Signed-off-by: Willy Tarreau <w@1wt.eu>
224-(cherry picked from commit e8ba5e106444fc78558f4ff26e9ce946f89216f4)
225-[wt: legacy not affected, http_wait_for_request() tests for empty
226- value (cf. proto_http.c:1099)]
227-Signed-off-by: Willy Tarreau <w@1wt.eu>
228----
229- reg-tests/http-messaging/h1_to_h1.vtc | 26 ++++++++++++++
230- reg-tests/http-messaging/h2_to_h1.vtc | 60 +++++++++++++++++++++++++++++++++
231- src/h1.c | 22 +++++++++---
232- src/h2.c | 22 +++++++++---
233- 4 files changed, 122 insertions(+), 8 deletions(-)
234-
235-diff --git a/reg-tests/http-messaging/h1_to_h1.vtc b/reg-tests/http-messaging/h1_to_h1.vtc
236-index 7694014..aeb3a60 100644
237---- a/reg-tests/http-messaging/h1_to_h1.vtc
238-+++ b/reg-tests/http-messaging/h1_to_h1.vtc
239-@@ -269,3 +269,29 @@ client c3h1 -connect ${h1_feh1_sock} {
240- # arrive here.
241- expect_close
242- } -run
243-+
244-+client c4h1 -connect ${h1_feh1_sock} {
245-+ # this request is invalid and advertises an invalid C-L ending with an
246-+ # empty value, which results in a stream error.
247-+ txreq \
248-+ -req "GET" \
249-+ -url "/test31.html" \
250-+ -hdr "content-length: 0," \
251-+ -hdr "connection: close"
252-+ rxresp
253-+ expect resp.status == 400
254-+ expect_close
255-+} -run
256-+
257-+client c5h1 -connect ${h1_feh1_sock} {
258-+ # this request is invalid and advertises an empty C-L, which results
259-+ # in a stream error.
260-+ txreq \
261-+ -req "GET" \
262-+ -url "/test41.html" \
263-+ -hdr "content-length:" \
264-+ -hdr "connection: close"
265-+ rxresp
266-+ expect resp.status == 400
267-+ expect_close
268-+} -run
269-diff --git a/reg-tests/http-messaging/h2_to_h1.vtc b/reg-tests/http-messaging/h2_to_h1.vtc
270-index 481aded..3ed3751 100644
271---- a/reg-tests/http-messaging/h2_to_h1.vtc
272-+++ b/reg-tests/http-messaging/h2_to_h1.vtc
273-@@ -10,6 +10,8 @@ barrier b1 cond 2 -cyclic
274- barrier b2 cond 2 -cyclic
275- barrier b3 cond 2 -cyclic
276- barrier b4 cond 2 -cyclic
277-+barrier b5 cond 2 -cyclic
278-+barrier b6 cond 2 -cyclic
279-
280- server s1 {
281- rxreq
282-@@ -31,6 +33,12 @@ server s1 {
283-
284- barrier b4 sync
285- # the next request is never received
286-+
287-+ barrier b5 sync
288-+ # the next request is never received
289-+
290-+ barrier b6 sync
291-+ # the next request is never received
292- } -repeat 2 -start
293-
294- haproxy h1 -conf {
295-@@ -115,6 +123,32 @@ client c1h2 -connect ${h1_feh2_sock} {
296- txdata -data "this is sent and ignored"
297- rxrst
298- } -run
299-+
300-+ # fifth request is invalid and advertises an invalid C-L ending with an
301-+ # empty value, which results in a stream error.
302-+ stream 9 {
303-+ barrier b5 sync
304-+ txreq \
305-+ -req "GET" \
306-+ -scheme "https" \
307-+ -url "/test5.html" \
308-+ -hdr "content-length" "0," \
309-+ -nostrend
310-+ rxrst
311-+ } -run
312-+
313-+ # sixth request is invalid and advertises an empty C-L, which results
314-+ # in a stream error.
315-+ stream 11 {
316-+ barrier b6 sync
317-+ txreq \
318-+ -req "GET" \
319-+ -scheme "https" \
320-+ -url "/test6.html" \
321-+ -hdr "content-length" "" \
322-+ -nostrend
323-+ rxrst
324-+ } -run
325- } -run
326-
327- # HEAD requests : don't work well yet
328-@@ -257,4 +291,30 @@ client c3h2 -connect ${h1_feh2_sock} {
329- txdata -data "this is sent and ignored"
330- rxrst
331- } -run
332-+
333-+ # fifth request is invalid and advertises invalid C-L ending with an
334-+ # empty value, which results in a stream error.
335-+ stream 9 {
336-+ barrier b5 sync
337-+ txreq \
338-+ -req "POST" \
339-+ -scheme "https" \
340-+ -url "/test25.html" \
341-+ -hdr "content-length" "0," \
342-+ -nostrend
343-+ rxrst
344-+ } -run
345-+
346-+ # sixth request is invalid and advertises an empty C-L, which results
347-+ # in a stream error.
348-+ stream 11 {
349-+ barrier b6 sync
350-+ txreq \
351-+ -req "POST" \
352-+ -scheme "https" \
353-+ -url "/test26.html" \
354-+ -hdr "content-length" "" \
355-+ -nostrend
356-+ rxrst
357-+ } -run
358- } -run
359-diff --git a/src/h1.c b/src/h1.c
360-index 69a7ced..248e315 100644
361---- a/src/h1.c
362-+++ b/src/h1.c
363-@@ -32,13 +32,20 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value)
364- int not_first = !!(h1m->flags & H1_MF_CLEN);
365- struct ist word;
366-
367-- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment
368-+ word.ptr = value->ptr;
369- e = value->ptr + value->len;
370-
371-- while (++word.ptr < e) {
372-- /* skip leading delimitor and blanks */
373-- if (unlikely(HTTP_IS_LWS(*word.ptr)))
374-+ while (1) {
375-+ if (word.ptr >= e) {
376-+ /* empty header or empty value */
377-+ goto fail;
378-+ }
379-+
380-+ /* skip leading delimiter and blanks */
381-+ if (unlikely(HTTP_IS_LWS(*word.ptr))) {
382-+ word.ptr++;
383- continue;
384-+ }
385-
386- /* digits only now */
387- for (cl = 0, n = word.ptr; n < e; n++) {
388-@@ -77,6 +84,13 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value)
389- h1m->flags |= H1_MF_CLEN;
390- h1m->curr_len = h1m->body_len = cl;
391- *value = word;
392-+
393-+ /* Now either n==e and we're done, or n points to the comma,
394-+ * and we skip it and continue.
395-+ */
396-+ if (n++ == e)
397-+ break;
398-+
399- word.ptr = n;
400- }
401- /* here we've reached the end with a single value or a series of
402-diff --git a/src/h2.c b/src/h2.c
403-index bfc0bda..77a9346 100644
404---- a/src/h2.c
405-+++ b/src/h2.c
406-@@ -458,13 +458,20 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
407- int not_first = !!(*msgf & H2_MSGF_BODY_CL);
408- struct ist word;
409-
410-- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment
411-+ word.ptr = value->ptr;
412- e = value->ptr + value->len;
413-
414-- while (++word.ptr < e) {
415-- /* skip leading delimitor and blanks */
416-- if (unlikely(HTTP_IS_LWS(*word.ptr)))
417-+ while (1) {
418-+ if (word.ptr >= e) {
419-+ /* empty header or empty value */
420-+ goto fail;
421-+ }
422-+
423-+ /* skip leading delimiter and blanks */
424-+ if (unlikely(HTTP_IS_LWS(*word.ptr))) {
425-+ word.ptr++;
426- continue;
427-+ }
428-
429- /* digits only now */
430- for (cl = 0, n = word.ptr; n < e; n++) {
431-@@ -503,6 +510,13 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
432- *msgf |= H2_MSGF_BODY_CL;
433- *body_len = cl;
434- *value = word;
435-+
436-+ /* Now either n==e and we're done, or n points to the comma,
437-+ * and we skip it and continue.
438-+ */
439-+ if (n++ == e)
440-+ break;
441-+
442- word.ptr = n;
443- }
444- /* here we've reached the end with a single value or a series of
445---
446-1.7.10.4
447-
448diff --git a/debian/patches/CVE-2023-40225-2.patch b/debian/patches/CVE-2023-40225-2.patch
449deleted file mode 100644
450index b4bfbc6..0000000
451--- a/debian/patches/CVE-2023-40225-2.patch
452+++ /dev/null
453@@ -1,150 +0,0 @@
454-From a90ecde20e53bb26a677b42f53db42900097af9a Mon Sep 17 00:00:00 2001
455-From: Willy Tarreau <w@1wt.eu>
456-Date: Wed, 9 Aug 2023 11:02:34 +0200
457-Subject: [PATCH] BUG/MINOR: http: skip leading zeroes in content-length
458- values
459-
460-Ben Kallus also noticed that we preserve leading zeroes on content-length
461-values. While this is totally valid, it would be safer to at least trim
462-them before passing the value, because a bogus server written to parse
463-using "strtol(value, NULL, 0)" could inadvertently take a leading zero
464-as a prefix for an octal value. While there is not much that can be done
465-to protect such servers in general (e.g. lack of check for overflows etc),
466-at least it's quite cheap to make sure the transmitted value is normalized
467-and not taken for an octal one.
468-
469-This is not really a bug, rather a missed opportunity to sanitize the
470-input, but is marked as a bug so that we don't forget to backport it to
471-stable branches.
472-
473-A combined regtest was added to h1or2_to_h1c which already validates
474-end-to-end syntax consistency on aggregate headers.
475-
476-(cherry picked from commit 22731762d9fe2c98d9e6c3942b1568266b23c69f)
477-Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
478-(cherry picked from commit c33738c7d4ed50e1758dbedd39dfe5bd929a5076)
479-Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
480-(cherry picked from commit 84462c9390c3efe95b748bb9fc3bd2edc3decd2f)
481-Signed-off-by: Amaury Denoyelle <adenoyelle@haproxy.com>
482-(cherry picked from commit e2e464018fda41758bd42d3bcd6ac623c88a110e)
483-[wt: applied the http.c to h2.c]
484-Signed-off-by: Willy Tarreau <w@1wt.eu>
485-(cherry picked from commit c48acd15011dc6c66977bc088065693f430821df)
486-Signed-off-by: Willy Tarreau <w@1wt.eu>
487-(cherry picked from commit 0ac860d19ad68bf4c1f6f8f3721801b7079368c6)
488-[wt: legacy mode was not changed as it's really not suitable for this,
489- since by then headers were known from their start and length and we
490- can't adjust them afterwards without breaking all indexing]
491-Signed-off-by: Willy Tarreau <w@1wt.eu>
492----
493- reg-tests/http-rules/h1or2_to_h1c.vtc | 16 ++++++++++++----
494- src/h1.c | 8 ++++++++
495- src/h2.c | 8 ++++++++
496- 3 files changed, 28 insertions(+), 4 deletions(-)
497-
498---- haproxy-2.0.31.orig/reg-tests/http-rules/h1or2_to_h1c.vtc
499-+++ haproxy-2.0.31/reg-tests/http-rules/h1or2_to_h1c.vtc
500-@@ -27,11 +27,11 @@ server s1 {
501- -body "This is a body"
502-
503- expect req.method == "GET"
504-- expect req.http.fe-sl1-crc == 992395575
505-- expect req.http.fe-sl2-crc == 1270056220
506-+ expect req.http.fe-sl1-crc == 1874847043
507-+ expect req.http.fe-sl2-crc == 1142278307
508- expect req.http.fe-hdr-crc == 1719311923
509-- expect req.http.be-sl1-crc == 2604236007
510-- expect req.http.be-sl2-crc == 4181358964
511-+ expect req.http.be-sl1-crc == 3455320059
512-+ expect req.http.be-sl2-crc == 2509326257
513- expect req.http.be-hdr-crc == 3634102538
514- } -repeat 2 -start
515-
516-@@ -53,6 +53,7 @@ haproxy h1 -conf {
517- http-request set-var(req.path) path
518- http-request set-var(req.query) query
519- http-request set-var(req.param) url_param(qs_arg)
520-+ http-request set-var(req.cl) req.fhdr(content-length)
521-
522- http-request set-header sl1 "sl1: "
523-
524-@@ -65,8 +66,10 @@ haproxy h1 -conf {
525-
526- http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;"
527- http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>"
528-+ http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>"
529- http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; "
530- http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>"
531-+ http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>"
532- http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;"
533- http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;"
534- http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;"
535-@@ -120,6 +123,7 @@ haproxy h1 -conf {
536- http-request set-var(req.path) path
537- http-request set-var(req.query) query
538- http-request set-var(req.param) url_param(qs_arg)
539-+ http-request set-var(req.cl) req.fhdr(content-length)
540-
541- http-request set-header sl1 "sl1: "
542-
543-@@ -132,8 +136,10 @@ haproxy h1 -conf {
544-
545- http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;"
546- http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>"
547-+ http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>"
548- http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; "
549- http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>"
550-+ http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>"
551- http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;"
552- http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;"
553- http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;"
554-@@ -171,6 +177,7 @@ client c1h1 -connect ${h1_feh1_sock} {
555- txreq \
556- -req GET \
557- -url /path/to/file.extension?qs_arg=qs_value \
558-+ -hdr "content-length: 000, 00" \
559- -hdr "hdr1: val1" \
560- -hdr "hdr2: val2a" \
561- -hdr "hdr2: val2b" \
562-@@ -205,6 +212,7 @@ client c1h2 -connect ${h1_feh2_sock} {
563- -req GET \
564- -scheme "https" \
565- -url /path/to/file.extension?qs_arg=qs_value \
566-+ -hdr "content-length" "000, 00" \
567- -hdr "hdr1" "val1" \
568- -hdr "hdr2" " val2a" \
569- -hdr "hdr2" " val2b" \
570---- haproxy-2.0.31.orig/src/h1.c
571-+++ haproxy-2.0.31/src/h1.c
572-@@ -56,6 +56,14 @@ int h1_parse_cont_len_header(struct h1m
573- goto fail;
574- break;
575- }
576-+
577-+ if (unlikely(!cl && n > word.ptr)) {
578-+ /* There was a leading zero before this digit,
579-+ * let's trim it.
580-+ */
581-+ word.ptr = n;
582-+ }
583-+
584- if (unlikely(cl > ULLONG_MAX / 10ULL))
585- goto fail; /* multiply overflow */
586- cl = cl * 10ULL;
587---- haproxy-2.0.31.orig/src/h2.c
588-+++ haproxy-2.0.31/src/h2.c
589-@@ -482,6 +482,14 @@ int h2_parse_cont_len_header(unsigned in
590- goto fail;
591- break;
592- }
593-+
594-+ if (unlikely(!cl && n > word.ptr)) {
595-+ /* There was a leading zero before this digit,
596-+ * let's trim it.
597-+ */
598-+ word.ptr = n;
599-+ }
600-+
601- if (unlikely(cl > ULLONG_MAX / 10ULL))
602- goto fail; /* multiply overflow */
603- cl = cl * 10ULL;
604diff --git a/debian/patches/series b/debian/patches/series
605index 339dd4f..0945bb0 100644
606--- a/debian/patches/series
607+++ b/debian/patches/series
608@@ -1,5 +1,3 @@
609 0002-Use-dpkg-buildflags-to-build-halog.patch
610 haproxy.service-start-after-syslog.patch
611 haproxy.service-add-documentation.patch
612-CVE-2023-40225-1.patch
613-CVE-2023-40225-2.patch
614diff --git a/doc/configuration.txt b/doc/configuration.txt
615index 4c7c59e..850f224 100644
616--- a/doc/configuration.txt
617+++ b/doc/configuration.txt
618@@ -3,7 +3,7 @@
619 Configuration Manual
620 ----------------------
621 version 2.0
622- 2023/02/14
623+ 2023/08/19
624
625
626 This document covers the configuration language as implemented in the version
627@@ -650,6 +650,7 @@ The following keywords are supported in the "global" section :
628 - tune.h2.header-table-size
629 - tune.h2.initial-window-size
630 - tune.h2.max-concurrent-streams
631+ - tune.h2.max-frame-size
632 - tune.http.cookielen
633 - tune.http.logurilen
634 - tune.http.maxhdr
635@@ -2088,7 +2089,8 @@ peers <peersect>
636 Creates a new peer list with name <peersect>. It is an independent section,
637 which is referenced by one or more stick-tables.
638
639-bind [<address>]:<port_range> [, ...] [param*]
640+bind [<address>]:port [param*]
641+bind /<path> [param*]
642 Defines the binding parameters of the local peer of this "peers" section.
643 Such lines are not supported with "peer" line in the same "peers" section.
644
645@@ -2117,15 +2119,16 @@ enabled
646 This re-enables a peers section which was previously disabled via the
647 "disabled" keyword.
648
649-peer <peername> <ip>:<port> [param*]
650+peer <peername> [<address>]:port [param*]
651+peer <peername> /<path> [param*]
652 Defines a peer inside a peers section.
653 If <peername> is set to the local peer name (by default hostname, or forced
654 using "-L" command line option), haproxy will listen for incoming remote peer
655- connection on <ip>:<port>. Otherwise, <ip>:<port> defines where to connect to
656- to join the remote peer, and <peername> is used at the protocol level to
657- identify and validate the remote peer on the server side.
658+ connection on the provided address. Otherwise, the address defines where to
659+ connect to to join the remote peer, and <peername> is used at the protocol
660+ level to identify and validate the remote peer on the server side.
661
662- During a soft restart, local peer <ip>:<port> is used by the old instance to
663+ During a soft restart, local peer address is used by the old instance to
664 connect the new one and initiate a complete replication (teaching process).
665
666 It is strongly recommended to have the exact same peers declaration on all
667@@ -2139,12 +2142,13 @@ peer <peername> <ip>:<port> [param*]
668 Note: "peer" keyword may transparently be replaced by "server" keyword (see
669 "server" keyword explanation below).
670
671-server <peername> [<ip>:<port>] [param*]
672+server <peername> [<address>:<port>] [param*]
673+server <peername> [/<path>] [param*]
674 As previously mentioned, "peer" keyword may be replaced by "server" keyword
675 with a support for all "server" parameters found in 5.2 paragraph that are
676- related to transport settings. If the underlying peer is local, <ip>:<port>
677- parameters must not be present; these parameters must be provided on a "bind"
678- line (see "bind" keyword of this "peers" section).
679+ related to transport settings. If the underlying peer is local, the address
680+ parameter must not be present; it must be provided on a "bind" line (see
681+ "bind" keyword of this "peers" section).
682
683 A number of "server" parameters are irrelevant for "peers" sections. Peers by
684 nature do not support dynamic host name resolution nor health checks, hence
685@@ -6082,7 +6086,8 @@ no option accept-invalid-http-request
686 remaining ones are blocked by default unless this option is enabled. This
687 option also relaxes the test on the HTTP version, it allows HTTP/0.9 requests
688 to pass through (no version specified) and multiple digits for both the major
689- and the minor version.
690+ and the minor version. Finally, this option also allows incoming URLs to
691+ contain fragment references ('#' after the path).
692
693 This option should never be enabled by default as it hides application bugs
694 and open security breaches. It should only be deployed after a problem has
695@@ -10574,6 +10579,9 @@ tcp-request inspect-delay <timeout>
696 Obviously this is unlikely to be very useful and might even be racy, so such
697 setups are not recommended.
698
699+ Note the inspection delay is shortened if an connection error or shutdown is
700+ experienced or if the request buffer appears as full.
701+
702 As soon as a rule matches, the request is released and continues as usual. If
703 the timeout is reached and no rule matches, the default policy will be to let
704 it pass through unaffected.
705@@ -16808,7 +16816,11 @@ path : string
706 information from databases and keep them in caches. Note that with outgoing
707 caches, it would be wiser to use "url" instead. With ACLs, it's typically
708 used to match exact file names (e.g. "/login.php"), or directory parts using
709- the derivative forms. See also the "url" and "base" fetch methods.
710+ the derivative forms. See also the "url" and "base" fetch methods. Please
711+ note that any fragment reference in the URI ('#' after the path) is strictly
712+ forbidden by the HTTP standard and will be rejected. However, if the frontend
713+ receiving the request has "option accept-invalid-http-request", then this
714+ fragment part will be accepted and will also appear in the path.
715
716 ACL derivatives :
717 path : exact string match
718@@ -16982,7 +16994,11 @@ url : string
719 "path" is preferred over using "url", because clients may send a full URL as
720 is normally done with proxies. The only real use is to match "*" which does
721 not match in "path", and for which there is already a predefined ACL. See
722- also "path" and "base".
723+ also "path" and "base". Please note that any fragment reference in the URI
724+ ('#' after the path) is strictly forbidden by the HTTP standard and will be
725+ rejected. However, if the frontend receiving the request has "option
726+ accept-invalid-http-request", then this fragment part will be accepted and
727+ will also appear in the url.
728
729 ACL derivatives :
730 url : exact string match
731@@ -17972,6 +17988,13 @@ Timings events in TCP mode:
732 header (empty line) was never seen, most likely because the server timeout
733 stroke before the server managed to process the request.
734
735+ - Td: this is the total transfer time of the response payload till the last
736+ byte sent to the client. In HTTP it starts after the last response header
737+ (after Tr).
738+
739+ The data sent are not guaranteed to be received by the client, they can be
740+ stuck in either the kernel or the network.
741+
742 - Ta: total active time for the HTTP request, between the moment the proxy
743 received the first byte of the request header and the emission of the last
744 byte of the response body. The exception is when the "logasap" option is
745diff --git a/include/common/h2.h b/include/common/h2.h
746index 6fbc2db..22acb94 100644
747--- a/include/common/h2.h
748+++ b/include/common/h2.h
749@@ -201,10 +201,10 @@ extern struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES];
750
751 /* various protocol processing functions */
752
753-int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len);
754+int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len, int relaxed);
755 int h2_make_h1_trailers(struct http_hdr *list, char *out, int osize);
756 int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len);
757-int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len);
758+int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed);
759 int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len);
760 int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx);
761
762diff --git a/include/common/ist.h b/include/common/ist.h
763index 0d8b457..9c6c37a 100644
764--- a/include/common/ist.h
765+++ b/include/common/ist.h
766@@ -204,6 +204,12 @@ static inline size_t istlen(const struct ist ist)
767 return ist.len;
768 }
769
770+/* returns the pointer to the end the string */
771+static inline char *istend(const struct ist ist)
772+{
773+ return (ist.ptr + ist.len);
774+}
775+
776 /* skips to next character in the string, always stops at the end */
777 static inline struct ist istnext(const struct ist ist)
778 {
779@@ -647,6 +653,53 @@ static inline const char *ist_find_ctl(const struct ist ist)
780 return NULL;
781 }
782
783+/* Returns a pointer to the first character found <ist> that belongs to the
784+ * range [min:max] inclusive, or NULL if none is present. The function is
785+ * optimized for strings having no such chars by processing up to sizeof(long)
786+ * bytes at once on architectures supporting efficient unaligned accesses.
787+ * Despite this it is not very fast (~0.43 byte/cycle) and should mostly be
788+ * used on low match probability when it can save a call to a much slower
789+ * function. Will not work for characters 0x80 and above. It's optimized for
790+ * min and max to be known at build time.
791+ */
792+static inline const char *ist_find_range(const struct ist ist, unsigned char min, unsigned char max)
793+{
794+ const union { unsigned long v; } __attribute__((packed)) *u;
795+ const char *curr = (void *)ist.ptr - sizeof(long);
796+ const char *last = curr + ist.len;
797+ unsigned long l1, l2;
798+
799+ /* easier with an exclusive boundary */
800+ max++;
801+
802+ do {
803+ curr += sizeof(long);
804+ if (curr > last)
805+ break;
806+ u = (void *)curr;
807+ /* add 0x<min><min><min><min>..<min> then subtract
808+ * 0x<max><max><max><max>..<max> to the value to generate a
809+ * carry in the lower byte if the byte contains a lower value.
810+ * If we generate a bit 7 that was not there, it means the byte
811+ * was min..max.
812+ */
813+ l2 = u->v;
814+ l1 = ~l2 & ((~0UL / 255) * 0x80); /* 0x808080...80 */
815+ l2 += (~0UL / 255) * min; /* 0x<min><min>..<min> */
816+ l2 -= (~0UL / 255) * max; /* 0x<max><max>..<max> */
817+ } while ((l1 & l2) == 0);
818+
819+ last += sizeof(long);
820+ if (__builtin_expect(curr < last, 0)) {
821+ do {
822+ if ((unsigned char)(*curr - min) < (unsigned char)(max - min))
823+ return curr;
824+ curr++;
825+ } while (curr < last);
826+ }
827+ return NULL;
828+}
829+
830 /* looks for first occurrence of character <chr> in string <ist> and returns
831 * the tail of the string starting with this character, or (ist.end,0) if not
832 * found.
833diff --git a/include/common/time.h b/include/common/time.h
834index 3b0f640..8e1d6e7 100644
835--- a/include/common/time.h
836+++ b/include/common/time.h
837@@ -60,7 +60,8 @@ extern THREAD_LOCAL unsigned int samp_time; /* total elapsed time over
838 extern THREAD_LOCAL unsigned int idle_time; /* total idle time over current sample */
839 extern THREAD_LOCAL struct timeval now; /* internal date is a monotonic function of real clock */
840 extern THREAD_LOCAL struct timeval date; /* the real current date */
841-extern struct timeval start_date; /* the process's start date */
842+extern struct timeval start_date; /* the process's start date */
843+extern struct timeval ready_date; /* date when the process was considered ready */
844 extern THREAD_LOCAL struct timeval before_poll; /* system date before calling poll() */
845 extern THREAD_LOCAL struct timeval after_poll; /* system date after leaving poll() */
846 extern volatile unsigned long long global_now;
847diff --git a/include/types/spoe.h b/include/types/spoe.h
848index 56224e1..aef387d 100644
849--- a/include/types/spoe.h
850+++ b/include/types/spoe.h
851@@ -272,6 +272,7 @@ struct spoe_agent {
852 struct freq_ctr conn_per_sec; /* connections per second */
853 struct freq_ctr err_per_sec; /* connetion errors per second */
854
855+ unsigned int idles; /* # of idle applets */
856 struct eb_root idle_applets; /* idle SPOE applets available to process data */
857 struct list applets; /* all SPOE applets for this agent */
858 struct list sending_queue; /* Queue of streams waiting to send data */
859diff --git a/reg-tests/http-messaging/h1_to_h1.vtc b/reg-tests/http-messaging/h1_to_h1.vtc
860index 7694014..aeb3a60 100644
861--- a/reg-tests/http-messaging/h1_to_h1.vtc
862+++ b/reg-tests/http-messaging/h1_to_h1.vtc
863@@ -269,3 +269,29 @@ client c3h1 -connect ${h1_feh1_sock} {
864 # arrive here.
865 expect_close
866 } -run
867+
868+client c4h1 -connect ${h1_feh1_sock} {
869+ # this request is invalid and advertises an invalid C-L ending with an
870+ # empty value, which results in a stream error.
871+ txreq \
872+ -req "GET" \
873+ -url "/test31.html" \
874+ -hdr "content-length: 0," \
875+ -hdr "connection: close"
876+ rxresp
877+ expect resp.status == 400
878+ expect_close
879+} -run
880+
881+client c5h1 -connect ${h1_feh1_sock} {
882+ # this request is invalid and advertises an empty C-L, which results
883+ # in a stream error.
884+ txreq \
885+ -req "GET" \
886+ -url "/test41.html" \
887+ -hdr "content-length:" \
888+ -hdr "connection: close"
889+ rxresp
890+ expect resp.status == 400
891+ expect_close
892+} -run
893diff --git a/reg-tests/http-messaging/h2_to_h1.vtc b/reg-tests/http-messaging/h2_to_h1.vtc
894index 481aded..3ed3751 100644
895--- a/reg-tests/http-messaging/h2_to_h1.vtc
896+++ b/reg-tests/http-messaging/h2_to_h1.vtc
897@@ -10,6 +10,8 @@ barrier b1 cond 2 -cyclic
898 barrier b2 cond 2 -cyclic
899 barrier b3 cond 2 -cyclic
900 barrier b4 cond 2 -cyclic
901+barrier b5 cond 2 -cyclic
902+barrier b6 cond 2 -cyclic
903
904 server s1 {
905 rxreq
906@@ -31,6 +33,12 @@ server s1 {
907
908 barrier b4 sync
909 # the next request is never received
910+
911+ barrier b5 sync
912+ # the next request is never received
913+
914+ barrier b6 sync
915+ # the next request is never received
916 } -repeat 2 -start
917
918 haproxy h1 -conf {
919@@ -115,6 +123,32 @@ client c1h2 -connect ${h1_feh2_sock} {
920 txdata -data "this is sent and ignored"
921 rxrst
922 } -run
923+
924+ # fifth request is invalid and advertises an invalid C-L ending with an
925+ # empty value, which results in a stream error.
926+ stream 9 {
927+ barrier b5 sync
928+ txreq \
929+ -req "GET" \
930+ -scheme "https" \
931+ -url "/test5.html" \
932+ -hdr "content-length" "0," \
933+ -nostrend
934+ rxrst
935+ } -run
936+
937+ # sixth request is invalid and advertises an empty C-L, which results
938+ # in a stream error.
939+ stream 11 {
940+ barrier b6 sync
941+ txreq \
942+ -req "GET" \
943+ -scheme "https" \
944+ -url "/test6.html" \
945+ -hdr "content-length" "" \
946+ -nostrend
947+ rxrst
948+ } -run
949 } -run
950
951 # HEAD requests : don't work well yet
952@@ -257,4 +291,30 @@ client c3h2 -connect ${h1_feh2_sock} {
953 txdata -data "this is sent and ignored"
954 rxrst
955 } -run
956+
957+ # fifth request is invalid and advertises invalid C-L ending with an
958+ # empty value, which results in a stream error.
959+ stream 9 {
960+ barrier b5 sync
961+ txreq \
962+ -req "POST" \
963+ -scheme "https" \
964+ -url "/test25.html" \
965+ -hdr "content-length" "0," \
966+ -nostrend
967+ rxrst
968+ } -run
969+
970+ # sixth request is invalid and advertises an empty C-L, which results
971+ # in a stream error.
972+ stream 11 {
973+ barrier b6 sync
974+ txreq \
975+ -req "POST" \
976+ -scheme "https" \
977+ -url "/test26.html" \
978+ -hdr "content-length" "" \
979+ -nostrend
980+ rxrst
981+ } -run
982 } -run
983diff --git a/reg-tests/http-rules/fragment_in_uri.vtc b/reg-tests/http-rules/fragment_in_uri.vtc
984new file mode 100644
985index 0000000..6217513
986--- /dev/null
987+++ b/reg-tests/http-rules/fragment_in_uri.vtc
988@@ -0,0 +1,35 @@
989+varnishtest "check for fragments in URL"
990+#REQUIRE_VERSION=2.0
991+
992+# This reg-test checks that '#' is properly blocked in requests
993+
994+feature ignore_unknown_macro
995+
996+server s1 {
997+ rxreq
998+ txresp -hdr "connection: close"
999+} -start
1000+
1001+haproxy h1 -conf {
1002+ global
1003+
1004+ defaults
1005+ mode http
1006+ timeout connect 1s
1007+ timeout client 1s
1008+ timeout server 1s
1009+
1010+ frontend fe_fragment_block
1011+ bind "fd@${fe_fragment_block}"
1012+ default_backend be
1013+
1014+ backend be
1015+ server s1 ${s1_addr}:${s1_port}
1016+
1017+} -start
1018+
1019+client c11 -connect ${h1_fe_fragment_block_sock} {
1020+ txreq -url "/#foo"
1021+ rxresp
1022+ expect resp.status == 400
1023+} -run
1024diff --git a/reg-tests/http-rules/h1or2_to_h1c.vtc b/reg-tests/http-rules/h1or2_to_h1c.vtc
1025index 16e2639..e6b7df4 100644
1026--- a/reg-tests/http-rules/h1or2_to_h1c.vtc
1027+++ b/reg-tests/http-rules/h1or2_to_h1c.vtc
1028@@ -27,11 +27,11 @@ server s1 {
1029 -body "This is a body"
1030
1031 expect req.method == "GET"
1032- expect req.http.fe-sl1-crc == 992395575
1033- expect req.http.fe-sl2-crc == 1270056220
1034+ expect req.http.fe-sl1-crc == 1874847043
1035+ expect req.http.fe-sl2-crc == 1142278307
1036 expect req.http.fe-hdr-crc == 1719311923
1037- expect req.http.be-sl1-crc == 2604236007
1038- expect req.http.be-sl2-crc == 4181358964
1039+ expect req.http.be-sl1-crc == 3455320059
1040+ expect req.http.be-sl2-crc == 2509326257
1041 expect req.http.be-hdr-crc == 3634102538
1042 } -repeat 2 -start
1043
1044@@ -53,6 +53,7 @@ haproxy h1 -conf {
1045 http-request set-var(req.path) path
1046 http-request set-var(req.query) query
1047 http-request set-var(req.param) url_param(qs_arg)
1048+ http-request set-var(req.cl) req.fhdr(content-length)
1049
1050 http-request set-header sl1 "sl1: "
1051
1052@@ -65,8 +66,10 @@ haproxy h1 -conf {
1053
1054 http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;"
1055 http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>"
1056+ http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>"
1057 http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; "
1058 http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>"
1059+ http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>"
1060 http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;"
1061 http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;"
1062 http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;"
1063@@ -120,6 +123,7 @@ haproxy h1 -conf {
1064 http-request set-var(req.path) path
1065 http-request set-var(req.query) query
1066 http-request set-var(req.param) url_param(qs_arg)
1067+ http-request set-var(req.cl) req.fhdr(content-length)
1068
1069 http-request set-header sl1 "sl1: "
1070
1071@@ -132,8 +136,10 @@ haproxy h1 -conf {
1072
1073 http-request set-header sl1 "%[req.fhdr(sl1)] method=<%[var(req.method)]>; uri=<%[var(req.uri)]>; path=<%[var(req.path)]>;"
1074 http-request set-header sl1 "%[req.fhdr(sl1)] query=<%[var(req.query)]>; param=<%[var(req.param)]>"
1075+ http-request set-header sl1 "%[req.fhdr(sl1)] cl=<%[var(req.cl)]>"
1076 http-request set-header sl2 "%[req.fhdr(sl2)] method=<%[method]>; uri=<%[url]>; path=<%[path]>; "
1077 http-request set-header sl2 "%[req.fhdr(sl2)] query=<%[query]>; param=<%[url_param(qs_arg)]>"
1078+ http-request set-header sl2 "%[req.fhdr(sl2)] cl=<%[req.fhdr(content-length)]>"
1079 http-request set-header hdr "%[req.fhdr(hdr)] hdr1=<%[req.hdr(hdr1)]>; fhdr1=<%[req.fhdr(hdr1)]>;"
1080 http-request set-header hdr "%[req.fhdr(hdr)] hdr2=<%[req.hdr(hdr2)]>; fhdr2=<%[req.fhdr(hdr2)]>;"
1081 http-request set-header hdr "%[req.fhdr(hdr)] hdr3=<%[req.hdr(hdr3)]>; fhdr3=<%[req.fhdr(hdr3)]>;"
1082@@ -171,6 +177,7 @@ client c1h1 -connect ${h1_feh1_sock} {
1083 txreq \
1084 -req GET \
1085 -url /path/to/file.extension?qs_arg=qs_value \
1086+ -hdr "content-length: 000, 00" \
1087 -hdr "hdr1: val1" \
1088 -hdr "hdr2: val2a" \
1089 -hdr "hdr2: val2b" \
1090@@ -205,6 +212,7 @@ client c1h2 -connect ${h1_feh2_sock} {
1091 -req GET \
1092 -scheme "https" \
1093 -url /path/to/file.extension?qs_arg=qs_value \
1094+ -hdr "content-length" "000, 00" \
1095 -hdr "hdr1" "val1" \
1096 -hdr "hdr2" " val2a" \
1097 -hdr "hdr2" " val2b" \
1098diff --git a/scripts/publish-release b/scripts/publish-release
1099index d785244..f356076 100755
1100--- a/scripts/publish-release
1101+++ b/scripts/publish-release
1102@@ -22,6 +22,9 @@ NEW=
1103 DIR=
1104 DOC=( )
1105
1106+# need to have group write on emitted files for others to update
1107+umask 002
1108+
1109 die() {
1110 [ "$#" -eq 0 ] || echo "$*" >&2
1111 exit 1
1112diff --git a/src/checks.c b/src/checks.c
1113index 06d6bff..b9f3c9d 100644
1114--- a/src/checks.c
1115+++ b/src/checks.c
1116@@ -2517,6 +2517,7 @@ static int start_check_task(struct check *check, int mininter,
1117 {
1118 struct task *t;
1119 unsigned long thread_mask = MAX_THREADS_MASK;
1120+ unsigned long boottime = tv_ms_remain(&start_date, &ready_date);
1121
1122 if (check->type == PR_O2_EXT_CHK)
1123 thread_mask = 1;
1124@@ -2535,11 +2536,19 @@ static int start_check_task(struct check *check, int mininter,
1125 if (mininter < srv_getinter(check))
1126 mininter = srv_getinter(check);
1127
1128+ if (global.spread_checks > 0) {
1129+ int rnd;
1130+
1131+ rnd = srv_getinter(check) * global.spread_checks / 100;
1132+ rnd -= (int) (2 * rnd * (ha_random32() / 4294967295.0));
1133+ mininter += rnd;
1134+ }
1135+
1136 if (global.max_spread_checks && mininter > global.max_spread_checks)
1137 mininter = global.max_spread_checks;
1138
1139 /* check this every ms */
1140- t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
1141+ t->expire = tick_add(now_ms, MS_TO_TICKS(boottime + mininter * srvpos / nbcheck));
1142 check->start = now;
1143 task_queue(t);
1144
1145diff --git a/src/chunk.c b/src/chunk.c
1146index 100783e..b62e323 100644
1147--- a/src/chunk.c
1148+++ b/src/chunk.c
1149@@ -153,15 +153,19 @@ int chunk_printf(struct buffer *chk, const char *fmt, ...)
1150 int chunk_appendf(struct buffer *chk, const char *fmt, ...)
1151 {
1152 va_list argp;
1153+ size_t room;
1154 int ret;
1155
1156 if (!chk->area || !chk->size)
1157 return 0;
1158
1159+ room = chk->size - chk->data;
1160+ if (!room)
1161+ return chk->data;
1162+
1163 va_start(argp, fmt);
1164- ret = vsnprintf(chk->area + chk->data, chk->size - chk->data, fmt,
1165- argp);
1166- if (ret >= chk->size - chk->data)
1167+ ret = vsnprintf(chk->area + chk->data, room, fmt, argp);
1168+ if (ret >= room)
1169 /* do not copy anything in case of truncation */
1170 chk->area[chk->data] = 0;
1171 else
1172diff --git a/src/debug.c b/src/debug.c
1173index b35bff5..4303d04 100644
1174--- a/src/debug.c
1175+++ b/src/debug.c
1176@@ -164,9 +164,10 @@ void ha_task_dump(struct buffer *buf, const struct task *task, const char *pfx)
1177 if (hlua && hlua->T) {
1178 chunk_appendf(buf, "stack traceback:\n ");
1179 append_prefixed_str(buf, hlua_traceback(hlua->T, "\n "), pfx, '\n', 0);
1180- b_putchr(buf, '\n');
1181 }
1182- else
1183+
1184+ /* we may need to terminate the current line */
1185+ if (*b_peek(buf, b_data(buf)-1) != '\n')
1186 b_putchr(buf, '\n');
1187 #endif
1188 }
1189diff --git a/src/filters.c b/src/filters.c
1190index a7327e8..8027d3e 100644
1191--- a/src/filters.c
1192+++ b/src/filters.c
1193@@ -292,10 +292,9 @@ flt_init_all()
1194 int err_code = 0;
1195
1196 for (px = proxies_list; px; px = px->next) {
1197- if (px->state == PR_STSTOPPED) {
1198- flt_deinit(px);
1199+ if (px->state == PR_STSTOPPED)
1200 continue;
1201- }
1202+
1203 err_code |= flt_init(px);
1204 if (err_code & (ERR_ABORT|ERR_FATAL)) {
1205 ha_alert("Failed to initialize filters for proxy '%s'.\n",
1206diff --git a/src/flt_spoe.c b/src/flt_spoe.c
1207index 128461b..d329f9f 100644
1208--- a/src/flt_spoe.c
1209+++ b/src/flt_spoe.c
1210@@ -1276,6 +1276,7 @@ spoe_release_appctx(struct appctx *appctx)
1211 if (appctx->st0 == SPOE_APPCTX_ST_IDLE) {
1212 eb32_delete(&spoe_appctx->node);
1213 _HA_ATOMIC_SUB(&agent->counters.idles, 1);
1214+ agent->rt[tid].idles--;
1215 }
1216
1217 appctx->st0 = SPOE_APPCTX_ST_END;
1218@@ -1489,6 +1490,7 @@ spoe_handle_connecting_appctx(struct appctx *appctx)
1219
1220 default:
1221 _HA_ATOMIC_ADD(&agent->counters.idles, 1);
1222+ agent->rt[tid].idles++;
1223 appctx->st0 = SPOE_APPCTX_ST_IDLE;
1224 SPOE_APPCTX(appctx)->node.key = 0;
1225 eb32_insert(&agent->rt[tid].idle_applets, &SPOE_APPCTX(appctx)->node);
1226@@ -1731,12 +1733,6 @@ spoe_handle_processing_appctx(struct appctx *appctx)
1227 (agent->b.be->nbpend ||
1228 (srv && (srv->nbpend || (srv->maxconn && srv->served >= srv_dynamic_maxconn(srv))))));
1229
1230- /* Don"t try to send new frame we are waiting for at lease a ack, in
1231- * sync mode or if applet must be closed ASAP
1232- */
1233- if (appctx->st0 == SPOE_APPCTX_ST_WAITING_SYNC_ACK || (close_asap && SPOE_APPCTX(appctx)->cur_fpa))
1234- skip_sending = 1;
1235-
1236 /* receiving_frame loop */
1237 while (!skip_receiving) {
1238 ret = spoe_handle_receiving_frame_appctx(appctx, &skip_receiving);
1239@@ -1757,6 +1753,12 @@ spoe_handle_processing_appctx(struct appctx *appctx)
1240 }
1241 }
1242
1243+ /* Don"t try to send new frame we are waiting for at lease a ack, in
1244+ * sync mode or if applet must be closed ASAP
1245+ */
1246+ if (appctx->st0 == SPOE_APPCTX_ST_WAITING_SYNC_ACK || (close_asap && SPOE_APPCTX(appctx)->cur_fpa))
1247+ skip_sending = 1;
1248+
1249 /* send_frame loop */
1250 while (!skip_sending && SPOE_APPCTX(appctx)->cur_fpa < agent->max_fpa) {
1251 ret = spoe_handle_sending_frame_appctx(appctx, &skip_sending);
1252@@ -1803,6 +1805,7 @@ spoe_handle_processing_appctx(struct appctx *appctx)
1253 goto next;
1254 }
1255 _HA_ATOMIC_ADD(&agent->counters.idles, 1);
1256+ agent->rt[tid].idles++;
1257 appctx->st0 = SPOE_APPCTX_ST_IDLE;
1258 eb32_insert(&agent->rt[tid].idle_applets, &SPOE_APPCTX(appctx)->node);
1259 }
1260@@ -1968,6 +1971,7 @@ spoe_handle_appctx(struct appctx *appctx)
1261
1262 case SPOE_APPCTX_ST_IDLE:
1263 _HA_ATOMIC_SUB(&agent->counters.idles, 1);
1264+ agent->rt[tid].idles--;
1265 eb32_delete(&SPOE_APPCTX(appctx)->node);
1266 if (stopping &&
1267 LIST_ISEMPTY(&agent->rt[tid].sending_queue) &&
1268@@ -2110,8 +2114,8 @@ spoe_queue_context(struct spoe_context *ctx)
1269 struct spoe_appctx *spoe_appctx;
1270
1271 /* Check if we need to create a new SPOE applet or not. */
1272- if (!eb_is_empty(&agent->rt[tid].idle_applets) &&
1273- (agent->rt[tid].processing == 1 || agent->rt[tid].processing < read_freq_ctr(&agent->rt[tid].processing_per_sec)))
1274+ if (agent->rt[tid].processing < agent->rt[tid].idles ||
1275+ agent->rt[tid].processing < read_freq_ctr(&agent->rt[tid].processing_per_sec))
1276 goto end;
1277
1278 SPOE_PRINTF(stderr, "%d.%06d [SPOE/%-15s] %s: stream=%p"
1279@@ -3038,6 +3042,14 @@ spoe_sig_stop(struct sig_handler *sh)
1280 while (p) {
1281 struct flt_conf *fconf;
1282
1283+ /* SPOE filter are not initialized for disabled proxoes. Move to
1284+ * the next one
1285+ */
1286+ if (p->state == PR_STSTOPPED) {
1287+ p = p->next;
1288+ continue;
1289+ }
1290+
1291 list_for_each_entry(fconf, &p->filter_configs, list) {
1292 struct spoe_config *conf;
1293 struct spoe_agent *agent;
1294@@ -3079,7 +3091,6 @@ spoe_init(struct proxy *px, struct flt_conf *fconf)
1295 conf->agent_fe.accept = frontend_accept;
1296 conf->agent_fe.srv = NULL;
1297 conf->agent_fe.timeout.client = TICK_ETERNITY;
1298- conf->agent_fe.default_target = &spoe_applet.obj_type;
1299 conf->agent_fe.fe_req_ana = AN_REQ_SWITCHING_RULES;
1300
1301 if (!sighandler_registered) {
1302@@ -3170,6 +3181,7 @@ spoe_check(struct proxy *px, struct flt_conf *fconf)
1303 conf->agent->rt[i].engine_id = NULL;
1304 conf->agent->rt[i].frame_size = conf->agent->max_frame_size;
1305 conf->agent->rt[i].processing = 0;
1306+ conf->agent->rt[i].idles = 0;
1307 LIST_INIT(&conf->agent->rt[i].applets);
1308 LIST_INIT(&conf->agent->rt[i].sending_queue);
1309 LIST_INIT(&conf->agent->rt[i].waiting_queue);
1310diff --git a/src/h1.c b/src/h1.c
1311index 69a7ced..30c49ea 100644
1312--- a/src/h1.c
1313+++ b/src/h1.c
1314@@ -32,13 +32,20 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value)
1315 int not_first = !!(h1m->flags & H1_MF_CLEN);
1316 struct ist word;
1317
1318- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment
1319+ word.ptr = value->ptr;
1320 e = value->ptr + value->len;
1321
1322- while (++word.ptr < e) {
1323- /* skip leading delimitor and blanks */
1324- if (unlikely(HTTP_IS_LWS(*word.ptr)))
1325+ while (1) {
1326+ if (word.ptr >= e) {
1327+ /* empty header or empty value */
1328+ goto fail;
1329+ }
1330+
1331+ /* skip leading delimiter and blanks */
1332+ if (unlikely(HTTP_IS_LWS(*word.ptr))) {
1333+ word.ptr++;
1334 continue;
1335+ }
1336
1337 /* digits only now */
1338 for (cl = 0, n = word.ptr; n < e; n++) {
1339@@ -49,6 +56,14 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value)
1340 goto fail;
1341 break;
1342 }
1343+
1344+ if (unlikely(!cl && n > word.ptr)) {
1345+ /* There was a leading zero before this digit,
1346+ * let's trim it.
1347+ */
1348+ word.ptr = n;
1349+ }
1350+
1351 if (unlikely(cl > ULLONG_MAX / 10ULL))
1352 goto fail; /* multiply overflow */
1353 cl = cl * 10ULL;
1354@@ -77,6 +92,13 @@ int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value)
1355 h1m->flags |= H1_MF_CLEN;
1356 h1m->curr_len = h1m->body_len = cl;
1357 *value = word;
1358+
1359+ /* Now either n==e and we're done, or n points to the comma,
1360+ * and we skip it and continue.
1361+ */
1362+ if (n++ == e)
1363+ break;
1364+
1365 word.ptr = n;
1366 }
1367 /* here we've reached the end with a single value or a series of
1368@@ -390,11 +412,11 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
1369 defined(__ARM_ARCH_7A__)
1370 /* speedup: skip bytes not between 0x21 and 0x7e inclusive */
1371 while (ptr <= end - sizeof(int)) {
1372- int x = *(int *)ptr - 0x21212121;
1373+ int x = *(int *)ptr - 0x24242424;
1374 if (x & 0x80808080)
1375 break;
1376
1377- x -= 0x5e5e5e5e;
1378+ x -= 0x5b5b5b5b;
1379 if (!(x & 0x80808080))
1380 break;
1381
1382@@ -406,8 +428,15 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
1383 goto http_msg_ood;
1384 }
1385 http_msg_rquri2:
1386- if (likely((unsigned char)(*ptr - 33) <= 93)) /* 33 to 126 included */
1387+ if (likely((unsigned char)(*ptr - 33) <= 93)) { /* 33 to 126 included */
1388+ if (*ptr == '#') {
1389+ if (h1m->err_pos < -1) /* PR_O2_REQBUG_OK not set */
1390+ goto invalid_char;
1391+ if (h1m->err_pos == -1) /* PR_O2_REQBUG_OK set: just log */
1392+ h1m->err_pos = ptr - start + skip;
1393+ }
1394 EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rquri2, http_msg_ood, state, H1_MSG_RQURI);
1395+ }
1396
1397 if (likely(HTTP_IS_SPHT(*ptr))) {
1398 sl.rq.u.len = ptr - sl.rq.u.ptr;
1399diff --git a/src/h2.c b/src/h2.c
1400index bfc0bda..a36b93b 100644
1401--- a/src/h2.c
1402+++ b/src/h2.c
1403@@ -61,6 +61,26 @@ static int has_forbidden_char(const struct ist ist, const char *start)
1404 return 0;
1405 }
1406
1407+/* Looks into <ist> for forbidden characters for :path values (0x00..0x1F,
1408+ * 0x20, 0x23), starting at pointer <start> which must be within <ist>.
1409+ * Returns non-zero if such a character is found, 0 otherwise. When run on
1410+ * unlikely header match, it's recommended to first check for the presence
1411+ * of control chars using ist_find_ctl().
1412+ */
1413+static inline int http_path_has_forbidden_char(const struct ist ist, const char *start)
1414+{
1415+ do {
1416+ if ((uint8_t)*start <= 0x23) {
1417+ if ((uint8_t)*start < 0x20)
1418+ return 1;
1419+ if ((1U << ((uint8_t)*start & 0x1F)) & ((1<<3) | (1<<0)))
1420+ return 1;
1421+ }
1422+ start++;
1423+ } while (start < istend(ist));
1424+ return 0;
1425+}
1426+
1427 /* Prepare the request line into <*ptr> (stopping at <end>) from pseudo headers
1428 * stored in <phdr[]>. <fields> indicates what was found so far. This should be
1429 * called once at the detection of the first general header field or at the end
1430@@ -158,8 +178,12 @@ static int h2_prepare_h1_reqline(uint32_t fields, struct ist *phdr, char **ptr,
1431 *
1432 * The Cookie header will be reassembled at the end, and for this, the <list>
1433 * will be used to create a linked list, so its contents may be destroyed.
1434+ *
1435+ * When <relaxed> is non-nul, some non-dangerous checks will be ignored. This
1436+ * is in order to satisfy "option accept-invalid-http-request" for
1437+ * interoperability purposes.
1438 */
1439-int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len)
1440+int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len, int relaxed)
1441 {
1442 struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
1443 char *out_end = out + osize;
1444@@ -196,9 +220,15 @@ int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int
1445 /* RFC7540#10.3: intermediaries forwarding to HTTP/1 must take care of
1446 * rejecting NUL, CR and LF characters.
1447 */
1448- ctl = ist_find_ctl(list[idx].v);
1449- if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl))
1450- goto fail;
1451+ if (phdr == H2_PHDR_IDX_PATH && !relaxed) {
1452+ ctl = ist_find_range(list[idx].v, 0, '#');
1453+ if (unlikely(ctl) && http_path_has_forbidden_char(list[idx].v, ctl))
1454+ goto fail;
1455+ } else {
1456+ ctl = ist_find_ctl(list[idx].v);
1457+ if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl))
1458+ goto fail;
1459+ }
1460
1461 if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) {
1462 /* insert a pseudo header by its index (in phdr) and value (in value) */
1463@@ -458,13 +488,20 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
1464 int not_first = !!(*msgf & H2_MSGF_BODY_CL);
1465 struct ist word;
1466
1467- word.ptr = value->ptr - 1; // -1 for next loop's pre-increment
1468+ word.ptr = value->ptr;
1469 e = value->ptr + value->len;
1470
1471- while (++word.ptr < e) {
1472- /* skip leading delimitor and blanks */
1473- if (unlikely(HTTP_IS_LWS(*word.ptr)))
1474+ while (1) {
1475+ if (word.ptr >= e) {
1476+ /* empty header or empty value */
1477+ goto fail;
1478+ }
1479+
1480+ /* skip leading delimiter and blanks */
1481+ if (unlikely(HTTP_IS_LWS(*word.ptr))) {
1482+ word.ptr++;
1483 continue;
1484+ }
1485
1486 /* digits only now */
1487 for (cl = 0, n = word.ptr; n < e; n++) {
1488@@ -475,6 +512,14 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
1489 goto fail;
1490 break;
1491 }
1492+
1493+ if (unlikely(!cl && n > word.ptr)) {
1494+ /* There was a leading zero before this digit,
1495+ * let's trim it.
1496+ */
1497+ word.ptr = n;
1498+ }
1499+
1500 if (unlikely(cl > ULLONG_MAX / 10ULL))
1501 goto fail; /* multiply overflow */
1502 cl = cl * 10ULL;
1503@@ -503,6 +548,13 @@ int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned lon
1504 *msgf |= H2_MSGF_BODY_CL;
1505 *body_len = cl;
1506 *value = word;
1507+
1508+ /* Now either n==e and we're done, or n points to the comma,
1509+ * and we skip it and continue.
1510+ */
1511+ if (n++ == e)
1512+ break;
1513+
1514 word.ptr = n;
1515 }
1516 /* here we've reached the end with a single value or a series of
1517@@ -626,8 +678,12 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr,
1518 *
1519 * The Cookie header will be reassembled at the end, and for this, the <list>
1520 * will be used to create a linked list, so its contents may be destroyed.
1521+ *
1522+ * When <relaxed> is non-nul, some non-dangerous checks will be ignored. This
1523+ * is in order to satisfy "option accept-invalid-http-request" for
1524+ * interoperability purposes.
1525 */
1526-int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len)
1527+int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed)
1528 {
1529 struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
1530 uint32_t fields; /* bit mask of H2_PHDR_FND_* */
1531@@ -664,11 +720,18 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
1532 }
1533
1534 /* RFC7540#10.3: intermediaries forwarding to HTTP/1 must take care of
1535- * rejecting NUL, CR and LF characters.
1536+ * rejecting NUL, CR and LF characters. For :path we reject all CTL
1537+ * chars, spaces, and '#'.
1538 */
1539- ctl = ist_find_ctl(list[idx].v);
1540- if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl))
1541- goto fail;
1542+ if (phdr == H2_PHDR_IDX_PATH && !relaxed) {
1543+ ctl = ist_find_range(list[idx].v, 0, '#');
1544+ if (unlikely(ctl) && http_path_has_forbidden_char(list[idx].v, ctl))
1545+ goto fail;
1546+ } else {
1547+ ctl = ist_find_ctl(list[idx].v);
1548+ if (unlikely(ctl) && has_forbidden_char(list[idx].v, ctl))
1549+ goto fail;
1550+ }
1551
1552 if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) {
1553 /* insert a pseudo header by its index (in phdr) and value (in value) */
1554diff --git a/src/haproxy.c b/src/haproxy.c
1555index f6166d4..b22e954 100644
1556--- a/src/haproxy.c
1557+++ b/src/haproxy.c
1558@@ -1994,7 +1994,18 @@ static void init(int argc, char **argv)
1559 }
1560 }
1561
1562+ /* update the ready date that will be used to count the startup time
1563+ * during config checks (e.g. to schedule certain tasks if needed)
1564+ */
1565+ gettimeofday(&date, NULL);
1566+ ready_date = date;
1567+
1568 err_code |= check_config_validity();
1569+
1570+ /* update the ready date to also account for the check time */
1571+ gettimeofday(&date, NULL);
1572+ ready_date = date;
1573+
1574 if (err_code & (ERR_ABORT|ERR_FATAL)) {
1575 ha_alert("Fatal errors found in configuration.\n");
1576 exit(1);
1577@@ -2565,6 +2576,7 @@ void deinit(void)
1578 free(rdr->cond);
1579 }
1580 free(rdr->rdr_str);
1581+ free(rdr->cookie_str);
1582 list_for_each_entry_safe(lf, lfb, &rdr->rdr_fmt, list) {
1583 LIST_DEL(&lf->list);
1584 free(lf);
1585@@ -2664,6 +2676,7 @@ void deinit(void)
1586 LIST_DEL(&l->by_bind);
1587 free(l->name);
1588 free(l->counters);
1589+ free(l->interface);
1590 free(l);
1591 }
1592
1593@@ -3258,6 +3271,10 @@ int main(int argc, char **argv)
1594 argv[0], (int)limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock);
1595 }
1596
1597+ /* update the ready date a last time to also account for final setup time */
1598+ gettimeofday(&date, NULL);
1599+ ready_date = date;
1600+
1601 if (global.mode & (MODE_DAEMON | MODE_MWORKER | MODE_MWORKER_WAIT)) {
1602 struct proxy *px;
1603 struct peers *curpeers;
1604@@ -3323,7 +3340,7 @@ int main(int argc, char **argv)
1605 list_for_each_entry(child, &proc_list, list) {
1606 if (child->relative_pid == relative_pid &&
1607 child->reloads == 0 && child->options & PROC_O_TYPE_WORKER) {
1608- child->timestamp = now.tv_sec;
1609+ child->timestamp = date.tv_sec;
1610 child->pid = ret;
1611 child->version = strdup(haproxy_version);
1612 break;
1613diff --git a/src/hlua.c b/src/hlua.c
1614index bd8a679..b97442d 100644
1615--- a/src/hlua.c
1616+++ b/src/hlua.c
1617@@ -3622,7 +3622,9 @@ __LJMP static int hlua_applet_tcp_set_var(lua_State *L)
1618 memset(&smp, 0, sizeof(smp));
1619 hlua_lua2smp(L, 3, &smp);
1620
1621- /* Store the sample in a variable. */
1622+ /* Store the sample in a variable. We don't need to dup the smp, vars API
1623+ * already takes care of duplicating dynamic var data.
1624+ */
1625 smp_set_owner(&smp, s->be, s->sess, s, 0);
1626 vars_set_by_name(name, len, &smp);
1627 return 0;
1628@@ -4158,7 +4160,9 @@ __LJMP static int hlua_applet_http_set_var(lua_State *L)
1629 memset(&smp, 0, sizeof(smp));
1630 hlua_lua2smp(L, 3, &smp);
1631
1632- /* Store the sample in a variable. */
1633+ /* Store the sample in a variable. We don't need to dup the smp, vars API
1634+ * already takes care of duplicating dynamic var data.
1635+ */
1636 smp_set_owner(&smp, s->be, s->sess, s, 0);
1637 vars_set_by_name(name, len, &smp);
1638 return 0;
1639@@ -5768,7 +5772,9 @@ __LJMP static int hlua_set_var(lua_State *L)
1640 memset(&smp, 0, sizeof(smp));
1641 hlua_lua2smp(L, 3, &smp);
1642
1643- /* Store the sample in a variable. */
1644+ /* Store the sample in a variable. We don't need to dup the smp, vars API
1645+ * already takes care of duplicating dynamic var data.
1646+ */
1647 smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR);
1648 vars_set_by_name(name, len, &smp);
1649 return 0;
1650@@ -6507,6 +6513,10 @@ static int hlua_sample_conv_wrapper(const struct arg *arg_p, struct sample *smp,
1651
1652 /* Convert the returned value in sample. */
1653 hlua_lua2smp(stream->hlua->T, -1, smp);
1654+ /* dup the smp before popping the related lua value and
1655+ * returning it to haproxy
1656+ */
1657+ smp_dup(smp);
1658 lua_pop(stream->hlua->T, 1);
1659 return 1;
1660
1661@@ -6655,6 +6665,10 @@ static int hlua_sample_fetch_wrapper(const struct arg *arg_p, struct sample *smp
1662
1663 /* Convert the returned value in sample. */
1664 hlua_lua2smp(stream->hlua->T, -1, smp);
1665+ /* dup the smp before popping the related lua value and
1666+ * returning it to haproxy
1667+ */
1668+ smp_dup(smp);
1669 lua_pop(stream->hlua->T, 1);
1670
1671 /* Set the end of execution flag. */
1672diff --git a/src/http.c b/src/http.c
1673index 1ee891c..74833a8 100644
1674--- a/src/http.c
1675+++ b/src/http.c
1676@@ -449,7 +449,7 @@ const char *http_get_reason(unsigned int status)
1677 case 226: return "IM Used";
1678 case 300: return "Multiple Choices";
1679 case 301: return "Moved Permanently";
1680- case 302: return "Moved Temporarily";
1681+ case 302: return "Found";
1682 case 303: return "See Other";
1683 case 304: return "Not Modified";
1684 case 305: return "Use Proxy";
1685diff --git a/src/http_msg.c b/src/http_msg.c
1686index e07c81c..c38a0f0 100644
1687--- a/src/http_msg.c
1688+++ b/src/http_msg.c
1689@@ -716,11 +716,11 @@ const char *http_parse_reqline(struct http_msg *msg,
1690 defined(__ARM_ARCH_7A__)
1691 /* speedup: skip bytes not between 0x21 and 0x7e inclusive */
1692 while (ptr <= end - sizeof(int)) {
1693- int x = *(int *)ptr - 0x21212121;
1694+ int x = *(int *)ptr - 0x24242424;
1695 if (x & 0x80808080)
1696 break;
1697
1698- x -= 0x5e5e5e5e;
1699+ x -= 0x5b5b5b5b;
1700 if (!(x & 0x80808080))
1701 break;
1702
1703@@ -732,8 +732,15 @@ const char *http_parse_reqline(struct http_msg *msg,
1704 goto http_msg_ood;
1705 }
1706 http_msg_rquri2:
1707- if (likely((unsigned char)(*ptr - 33) <= 93)) /* 33 to 126 included */
1708+ if (likely((unsigned char)(*ptr - 33) <= 93)) { /* 33 to 126 included */
1709+ if (*ptr == '#') {
1710+ if (msg->err_pos < -1) /* PR_O2_REQBUG_OK not set */
1711+ goto invalid_char;
1712+ if (msg->err_pos == -1) /* PR_O2_REQBUG_OK set: just log */
1713+ msg->err_pos = ptr - msg_start;
1714+ }
1715 EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rquri2, http_msg_ood, state, HTTP_MSG_RQURI);
1716+ }
1717
1718 if (likely(HTTP_IS_SPHT(*ptr))) {
1719 msg->sl.rq.u_l = ptr - msg_start - msg->sl.rq.u;
1720diff --git a/src/log.c b/src/log.c
1721index a63ba61..708b4db 100644
1722--- a/src/log.c
1723+++ b/src/log.c
1724@@ -834,6 +834,10 @@ int parse_logsrv(char **args, struct list *logsrvs, int do_del, char **err)
1725 }
1726
1727 node = malloc(sizeof(*node));
1728+ if (!node) {
1729+ memprintf(err, "out of memory error");
1730+ goto error;
1731+ }
1732 memcpy(node, logsrv, sizeof(struct logsrv));
1733 node->ref = logsrv;
1734 LIST_INIT(&node->list);
1735diff --git a/src/mux_h1.c b/src/mux_h1.c
1736index 4c91672..426e996 100644
1737--- a/src/mux_h1.c
1738+++ b/src/mux_h1.c
1739@@ -553,8 +553,10 @@ static void h1_release(struct h1c *h1c)
1740 h1c->task = NULL;
1741 }
1742
1743- if (h1c->wait_event.tasklet)
1744+ if (h1c->wait_event.tasklet) {
1745 tasklet_free(h1c->wait_event.tasklet);
1746+ h1c->wait_event.tasklet = NULL;
1747+ }
1748
1749 h1s_destroy(h1c->h1s);
1750 if (conn) {
1751@@ -2210,7 +2212,8 @@ static int h1_process(struct h1c * h1c)
1752 if (!h1s_data_pending(h1s) && h1s && h1s->cs && h1s->cs->data_cb->wake &&
1753 (h1s->flags & H1S_F_REOS || h1c->flags & H1C_F_CS_ERROR ||
1754 conn->flags & (CO_FL_ERROR | CO_FL_SOCK_WR_SH))) {
1755- if (h1c->flags & H1C_F_CS_ERROR || ((conn->flags & CO_FL_ERROR) && !b_data(&h1c->ibuf)))
1756+ if (h1c->flags & H1C_F_CS_ERROR || ((conn->flags & CO_FL_ERROR) &&
1757+ ((h1s->cs->flags & (CS_FL_EOI|CS_FL_EOS)) || !b_data(&h1c->ibuf))))
1758 h1s->cs->flags |= CS_FL_ERROR;
1759 h1s->cs->data_cb->wake(h1s->cs);
1760 }
1761@@ -2496,6 +2499,9 @@ static void h1_shutw_conn(struct connection *conn)
1762 conn_xprt_shutw(conn);
1763 conn_sock_shutw(conn, (h1c && !(h1c->flags & H1C_F_ST_SILENT_SHUT)));
1764 h1c->flags = (h1c->flags & ~H1C_F_CS_SHUTW_NOW) | H1C_F_CS_SHUTDOWN;
1765+
1766+ if (h1c->wait_event.tasklet && !h1c->wait_event.events)
1767+ tasklet_wakeup(h1c->wait_event.tasklet);
1768 }
1769
1770 /* Called from the upper layer, to unsubscribe to events */
1771@@ -2600,15 +2606,21 @@ static size_t h1_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
1772
1773 if (!(h1c->flags & (H1C_F_OUT_FULL|H1C_F_OUT_ALLOC)))
1774 ret = h1_process_output(h1c, buf, count);
1775+
1776 if (!ret)
1777 break;
1778 total += ret;
1779 count -= ret;
1780+
1781 if ((h1c->wait_event.events & SUB_RETRY_SEND) || !h1_send(h1c))
1782 break;
1783+
1784+ if ((h1c->conn->flags & (CO_FL_ERROR|CO_FL_SOCK_WR_SH)))
1785+ break;
1786 }
1787
1788- if (h1c->flags & H1C_F_CS_ERROR)
1789+ if (h1c->flags & H1C_F_CS_ERROR || ((h1c->conn->flags & CO_FL_ERROR) &&
1790+ ((cs->flags & (CS_FL_EOI|CS_FL_EOS)) || !b_data(&h1c->ibuf))))
1791 cs->flags |= CS_FL_ERROR;
1792
1793 h1_refresh_timeout(h1c);
1794diff --git a/src/mux_h2.c b/src/mux_h2.c
1795index 3141b36..6aaaa3a 100644
1796--- a/src/mux_h2.c
1797+++ b/src/mux_h2.c
1798@@ -608,6 +608,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s
1799
1800 h2c->proxy = prx;
1801 h2c->task = NULL;
1802+ h2c->wait_event.tasklet = NULL;
1803 h2c->idle_start = now_ms;
1804 if (tick_isset(h2c->timeout)) {
1805 t = task_new(tid_bit);
1806@@ -2117,8 +2118,10 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
1807 if (h2s->st != H2_SS_CLOSED) {
1808 error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &body_len);
1809 /* unrecoverable error ? */
1810- if (h2c->st0 >= H2_CS_ERROR)
1811+ if (h2c->st0 >= H2_CS_ERROR) {
1812+ sess_log(h2c->conn->owner);
1813 goto out;
1814+ }
1815
1816 if (error == 0) {
1817 /* Demux not blocked because of the stream, it is an incomplete frame */
1818@@ -2131,6 +2134,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
1819 /* Failed to decode this frame (e.g. too large request)
1820 * but the HPACK decompressor is still synchronized.
1821 */
1822+ sess_log(h2c->conn->owner);
1823 h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
1824 h2c->st0 = H2_CS_FRAME_E;
1825 goto out;
1826@@ -2141,6 +2145,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
1827 * the data and send another RST.
1828 */
1829 error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
1830+ sess_log(h2c->conn->owner);
1831 h2s = (struct h2s*)h2_error_stream;
1832 goto send_rst;
1833 }
1834@@ -2156,8 +2161,10 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
1835 error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
1836
1837 /* unrecoverable error ? */
1838- if (h2c->st0 >= H2_CS_ERROR)
1839+ if (h2c->st0 >= H2_CS_ERROR) {
1840+ sess_log(h2c->conn->owner);
1841 goto out;
1842+ }
1843
1844 if (error <= 0) {
1845 if (error == 0) {
1846@@ -2170,6 +2177,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
1847 /* Failed to decode this stream (e.g. too large request)
1848 * but the HPACK decompressor is still synchronized.
1849 */
1850+ sess_log(h2c->conn->owner);
1851 h2s = (struct h2s*)h2_error_stream;
1852 goto send_rst;
1853 }
1854@@ -3436,7 +3444,7 @@ static void h2_detach(struct conn_stream *cs)
1855 /* refresh the timeout if none was active, so that the last
1856 * leaving stream may arm it.
1857 */
1858- if (!tick_isset(h2c->task->expire))
1859+ if (h2c->task && !tick_isset(h2c->task->expire))
1860 h2c_update_timeout(h2c);
1861 return;
1862 }
1863@@ -3905,10 +3913,12 @@ next_frame:
1864 if (h2c->flags & H2_CF_IS_BACK)
1865 outlen = h2_make_htx_response(list, htx, &msgf, body_len);
1866 else
1867- outlen = h2_make_htx_request(list, htx, &msgf, body_len);
1868+ outlen = h2_make_htx_request(list, htx, &msgf, body_len,
1869+ !!(((const struct session *)h2c->conn->owner)->fe->options2 & PR_O2_REQBUG_OK));
1870 } else {
1871 /* HTTP/1 mode */
1872- outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body_len);
1873+ outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body_len,
1874+ !!(((const struct session *)h2c->conn->owner)->fe->options2 & PR_O2_REQBUG_OK));
1875 if (outlen > 0)
1876 b_add(rxbuf, outlen);
1877 }
1878diff --git a/src/mworker.c b/src/mworker.c
1879index 487d071..8b646a7 100644
1880--- a/src/mworker.c
1881+++ b/src/mworker.c
1882@@ -173,6 +173,8 @@ int mworker_env_to_proc_list()
1883
1884 } else if (strncmp(subtoken, "fd=", 3) == 0) {
1885 child->ipc_fd[0] = atoi(subtoken+3);
1886+ if (child->ipc_fd[0] > -1)
1887+ global.maxsock++;
1888 } else if (strncmp(subtoken, "pid=", 4) == 0) {
1889 child->pid = atoi(subtoken+4);
1890 } else if (strncmp(subtoken, "rpid=", 5) == 0) {
1891@@ -388,6 +390,9 @@ static int mworker_pipe_register_per_thread()
1892 if (tid != 0)
1893 return 1;
1894
1895+ if (proc_self->ipc_fd[1] < 0) /* proc_self was incomplete and we can't find the socketpair */
1896+ return 1;
1897+
1898 fcntl(proc_self->ipc_fd[1], F_SETFL, O_NONBLOCK);
1899 /* In multi-tread, we need only one thread to process
1900 * events on the pipe with master
1901@@ -483,12 +488,15 @@ static int cli_io_handler_show_proc(struct appctx *appctx)
1902 struct stream_interface *si = appctx->owner;
1903 struct mworker_proc *child;
1904 int old = 0;
1905- int up = now.tv_sec - proc_self->timestamp;
1906+ int up = date.tv_sec - proc_self->timestamp;
1907 char *uptime = NULL;
1908
1909 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1910 return 1;
1911
1912+ if (up < 0) /* must never be negative because of clock drift */
1913+ up = 0;
1914+
1915 chunk_reset(&trash);
1916
1917 chunk_printf(&trash, "#%-14s %-15s %-15s %-15s %-15s %-15s\n", "<PID>", "<type>", "<relative PID>", "<reloads>", "<uptime>", "<version>");
1918@@ -501,7 +509,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx)
1919
1920 chunk_appendf(&trash, "# workers\n");
1921 list_for_each_entry(child, &proc_list, list) {
1922- up = now.tv_sec - child->timestamp;
1923+ up = date.tv_sec - child->timestamp;
1924+ if (up < 0) /* must never be negative because of clock drift */
1925+ up = 0;
1926
1927 if (!(child->options & PROC_O_TYPE_WORKER))
1928 continue;
1929@@ -523,7 +533,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx)
1930
1931 chunk_appendf(&trash, "# old workers\n");
1932 list_for_each_entry(child, &proc_list, list) {
1933- up = now.tv_sec - child->timestamp;
1934+ up = date.tv_sec - child->timestamp;
1935+ if (up <= 0) /* must never be negative because of clock drift */
1936+ up = 0;
1937
1938 if (!(child->options & PROC_O_TYPE_WORKER))
1939 continue;
1940@@ -543,7 +555,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx)
1941 chunk_appendf(&trash, "# programs\n");
1942 old = 0;
1943 list_for_each_entry(child, &proc_list, list) {
1944- up = now.tv_sec - child->timestamp;
1945+ up = date.tv_sec - child->timestamp;
1946+ if (up < 0) /* must never be negative because of clock drift */
1947+ up = 0;
1948
1949 if (!(child->options & PROC_O_TYPE_PROG))
1950 continue;
1951@@ -561,7 +575,9 @@ static int cli_io_handler_show_proc(struct appctx *appctx)
1952 if (old) {
1953 chunk_appendf(&trash, "# old programs\n");
1954 list_for_each_entry(child, &proc_list, list) {
1955- up = now.tv_sec - child->timestamp;
1956+ up = date.tv_sec - child->timestamp;
1957+ if (up < 0) /* must never be negative because of clock drift */
1958+ up = 0;
1959
1960 if (!(child->options & PROC_O_TYPE_PROG))
1961 continue;
1962diff --git a/src/namespace.c b/src/namespace.c
1963index 89a968e..ad53d1e 100644
1964--- a/src/namespace.c
1965+++ b/src/namespace.c
1966@@ -54,6 +54,7 @@ static void netns_sig_stop(struct sig_handler *sh)
1967 entry = container_of(node, struct netns_entry, node);
1968 free(entry->node.key);
1969 close(entry->fd);
1970+ free(entry);
1971 node = next;
1972 }
1973 }
1974diff --git a/src/proto_htx.c b/src/proto_htx.c
1975index 51fdd47..91fdbfb 100644
1976--- a/src/proto_htx.c
1977+++ b/src/proto_htx.c
1978@@ -2642,11 +2642,12 @@ int htx_transform_header_str(struct stream* s, struct channel *chn, struct htx *
1979 struct ist name, const char *str, struct my_regex *re, int action)
1980 {
1981 struct http_hdr_ctx ctx;
1982- struct buffer *output = get_trash_chunk();
1983
1984 /* find full header is action is ACT_HTTP_REPLACE_HDR */
1985 ctx.blk = NULL;
1986 while (http_find_header(htx, name, &ctx, (action == ACT_HTTP_REPLACE_HDR))) {
1987+ struct buffer *output = get_trash_chunk();
1988+
1989 if (!regex_exec_match2(re, ctx.value.ptr, ctx.value.len, MAX_MATCH, pmatch, 0))
1990 continue;
1991
1992diff --git a/src/proto_tcp.c b/src/proto_tcp.c
1993index fc26a7c..5882e6b 100644
1994--- a/src/proto_tcp.c
1995+++ b/src/proto_tcp.c
1996@@ -1917,8 +1917,10 @@ static int bind_parse_interface(char **args, int cur_arg, struct proxy *px, stru
1997 }
1998
1999 list_for_each_entry(l, &conf->listeners, by_bind) {
2000- if (l->addr.ss_family == AF_INET || l->addr.ss_family == AF_INET6)
2001+ if (l->addr.ss_family == AF_INET || l->addr.ss_family == AF_INET6) {
2002+ free(l->interface);
2003 l->interface = strdup(args[cur_arg + 1]);
2004+ }
2005 }
2006
2007 return 0;
2008diff --git a/src/proxy.c b/src/proxy.c
2009index cb5f8e1..d3d62ab 100644
2010--- a/src/proxy.c
2011+++ b/src/proxy.c
2012@@ -1022,8 +1022,8 @@ struct task *manage_proxy(struct task *t, void *context, unsigned short state)
2013 * to push to a new process and
2014 * we are free to flush the table.
2015 */
2016- stktable_trash_oldest(p->table, p->table->current);
2017- pool_gc(NULL);
2018+ if (stktable_trash_oldest(p->table, p->table->current))
2019+ pool_gc(NULL);
2020 }
2021 if (p->table->current) {
2022 /* some entries still remain, let's recheck in one second */
2023diff --git a/src/sample.c b/src/sample.c
2024index e713f29..6153832 100644
2025--- a/src/sample.c
2026+++ b/src/sample.c
2027@@ -2451,12 +2451,12 @@ static inline long long int arith_add(long long int a, long long int b)
2028 * +------+----------+----------+
2029 */
2030 if ((a ^ b) >= 0) {
2031- /* signs are differents. */
2032+ /* signs are same. */
2033 if (a < 0) {
2034 if (LLONG_MIN - a > b)
2035 return LLONG_MIN;
2036 }
2037- if (LLONG_MAX - a < b)
2038+ else if (LLONG_MAX - a < b)
2039 return LLONG_MAX;
2040 }
2041 return a + b;
2042diff --git a/src/server.c b/src/server.c
2043index bef825d..5f1510f 100644
2044--- a/src/server.c
2045+++ b/src/server.c
2046@@ -1832,6 +1832,7 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
2047 if (srv_tmpl)
2048 srv->srvrq = src->srvrq;
2049
2050+ srv->netns = src->netns;
2051 srv->check.via_socks4 = src->check.via_socks4;
2052 srv->socks4_addr = src->socks4_addr;
2053 }
2054@@ -3132,7 +3133,7 @@ static void srv_update_state(struct server *srv, int version, char **params)
2055 srv_adm_set_drain(srv);
2056 }
2057
2058- srv->last_change = date.tv_sec - srv_last_time_change;
2059+ srv->last_change = now.tv_sec - srv_last_time_change;
2060 srv->check.status = srv_check_status;
2061 srv->check.result = srv_check_result;
2062
2063@@ -4982,6 +4983,7 @@ static void srv_update_status(struct server *s)
2064 struct proxy *px = s->proxy;
2065 int prev_srv_count = s->proxy->srv_bck + s->proxy->srv_act;
2066 int srv_was_stopping = (s->cur_state == SRV_ST_STOPPING) || (s->cur_admin & SRV_ADMF_DRAIN);
2067+ enum srv_state srv_prev_state = s->cur_state;
2068 int log_level;
2069 struct buffer *tmptrash = NULL;
2070
2071@@ -4996,7 +4998,6 @@ static void srv_update_status(struct server *s)
2072 s->next_admin = s->cur_admin;
2073
2074 if ((s->cur_state != SRV_ST_STOPPED) && (s->next_state == SRV_ST_STOPPED)) {
2075- s->last_change = now.tv_sec;
2076 if (s->proxy->lbprm.set_server_status_down)
2077 s->proxy->lbprm.set_server_status_down(s);
2078
2079@@ -5027,13 +5028,9 @@ static void srv_update_status(struct server *s)
2080 free_trash_chunk(tmptrash);
2081 tmptrash = NULL;
2082 }
2083- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2084- set_backend_down(s->proxy);
2085-
2086 s->counters.down_trans++;
2087 }
2088 else if ((s->cur_state != SRV_ST_STOPPING) && (s->next_state == SRV_ST_STOPPING)) {
2089- s->last_change = now.tv_sec;
2090 if (s->proxy->lbprm.set_server_status_down)
2091 s->proxy->lbprm.set_server_status_down(s);
2092
2093@@ -5057,22 +5054,10 @@ static void srv_update_status(struct server *s)
2094 free_trash_chunk(tmptrash);
2095 tmptrash = NULL;
2096 }
2097-
2098- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2099- set_backend_down(s->proxy);
2100 }
2101 else if (((s->cur_state != SRV_ST_RUNNING) && (s->next_state == SRV_ST_RUNNING))
2102 || ((s->cur_state != SRV_ST_STARTING) && (s->next_state == SRV_ST_STARTING))) {
2103- if (s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) {
2104- if (s->proxy->last_change < now.tv_sec) // ignore negative times
2105- s->proxy->down_time += now.tv_sec - s->proxy->last_change;
2106- s->proxy->last_change = now.tv_sec;
2107- }
2108-
2109- if (s->cur_state == SRV_ST_STOPPED && s->last_change < now.tv_sec) // ignore negative times
2110- s->down_time += now.tv_sec - s->last_change;
2111
2112- s->last_change = now.tv_sec;
2113 if (s->next_state == SRV_ST_STARTING && s->warmup)
2114 task_schedule(s->warmup, tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20))));
2115
2116@@ -5118,9 +5103,6 @@ static void srv_update_status(struct server *s)
2117 free_trash_chunk(tmptrash);
2118 tmptrash = NULL;
2119 }
2120-
2121- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2122- set_backend_down(s->proxy);
2123 }
2124 else if (s->cur_eweight != s->next_eweight) {
2125 /* now propagate the status change to any LB algorithms */
2126@@ -5134,9 +5116,6 @@ static void srv_update_status(struct server *s)
2127 if (px->lbprm.set_server_status_down)
2128 px->lbprm.set_server_status_down(s);
2129 }
2130-
2131- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2132- set_backend_down(s->proxy);
2133 }
2134
2135 s->next_admin = next_admin;
2136@@ -5174,13 +5153,9 @@ static void srv_update_status(struct server *s)
2137 free_trash_chunk(tmptrash);
2138 tmptrash = NULL;
2139 }
2140- /* commit new admin status */
2141-
2142- s->cur_admin = s->next_admin;
2143 }
2144 else { /* server was still running */
2145 check->health = 0; /* failure */
2146- s->last_change = now.tv_sec;
2147
2148 s->next_state = SRV_ST_STOPPED;
2149 if (s->proxy->lbprm.set_server_status_down)
2150@@ -5213,9 +5188,6 @@ static void srv_update_status(struct server *s)
2151 free_trash_chunk(tmptrash);
2152 tmptrash = NULL;
2153 }
2154- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2155- set_backend_down(s->proxy);
2156-
2157 s->counters.down_trans++;
2158 }
2159 }
2160@@ -5235,7 +5207,6 @@ static void srv_update_status(struct server *s)
2161 * that the server might still be in drain mode, which is naturally dealt
2162 * with by the lower level functions.
2163 */
2164-
2165 if (s->check.state & CHK_ST_ENABLED) {
2166 s->check.state &= ~CHK_ST_PAUSED;
2167 check->health = check->rise; /* start OK but check immediately */
2168@@ -5248,7 +5219,6 @@ static void srv_update_status(struct server *s)
2169 s->next_state = SRV_ST_STOPPING;
2170 }
2171 else {
2172- s->last_change = now.tv_sec;
2173 s->next_state = SRV_ST_STARTING;
2174 if (s->slowstart > 0) {
2175 if (s->warmup)
2176@@ -5306,11 +5276,6 @@ static void srv_update_status(struct server *s)
2177 px->lbprm.set_server_status_down(s);
2178 }
2179
2180- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2181- set_backend_down(s->proxy);
2182- else if (!prev_srv_count && (s->proxy->srv_bck || s->proxy->srv_act))
2183- s->proxy->last_change = now.tv_sec;
2184-
2185 /* If the server is set with "on-marked-up shutdown-backup-sessions",
2186 * and it's not a backup server and its effective weight is > 0,
2187 * then it can accept new connections, so we shut down all streams
2188@@ -5380,15 +5345,12 @@ static void srv_update_status(struct server *s)
2189 }
2190 }
2191 /* don't report anything when leaving drain mode and remaining in maintenance */
2192-
2193- s->cur_admin = s->next_admin;
2194 }
2195
2196 if (!(s->next_admin & SRV_ADMF_MAINT)) {
2197 if (!(s->cur_admin & SRV_ADMF_DRAIN) && (s->next_admin & SRV_ADMF_DRAIN)) {
2198 /* drain state is applied only if not yet in maint */
2199
2200- s->last_change = now.tv_sec;
2201 if (px->lbprm.set_server_status_down)
2202 px->lbprm.set_server_status_down(s);
2203
2204@@ -5416,26 +5378,14 @@ static void srv_update_status(struct server *s)
2205 free_trash_chunk(tmptrash);
2206 tmptrash = NULL;
2207 }
2208-
2209- if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2210- set_backend_down(s->proxy);
2211 }
2212 else if ((s->cur_admin & SRV_ADMF_DRAIN) && !(s->next_admin & SRV_ADMF_DRAIN)) {
2213 /* OK completely leaving drain mode */
2214- if (s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) {
2215- if (s->proxy->last_change < now.tv_sec) // ignore negative times
2216- s->proxy->down_time += now.tv_sec - s->proxy->last_change;
2217- s->proxy->last_change = now.tv_sec;
2218- }
2219-
2220- if (s->last_change < now.tv_sec) // ignore negative times
2221- s->down_time += now.tv_sec - s->last_change;
2222- s->last_change = now.tv_sec;
2223 server_recalc_eweight(s, 0);
2224
2225 tmptrash = alloc_trash_chunk();
2226 if (tmptrash) {
2227- if (!(s->next_admin & SRV_ADMF_FDRAIN)) {
2228+ if (s->cur_admin & SRV_ADMF_FDRAIN) {
2229 chunk_printf(tmptrash,
2230 "%sServer %s/%s is %s (leaving forced drain)",
2231 s->flags & SRV_F_BACKUP ? "Backup " : "",
2232@@ -5499,15 +5449,38 @@ static void srv_update_status(struct server *s)
2233 free_trash_chunk(tmptrash);
2234 tmptrash = NULL;
2235 }
2236-
2237- /* commit new admin status */
2238-
2239- s->cur_admin = s->next_admin;
2240 }
2241 }
2242
2243 /* Re-set log strings to empty */
2244 *s->adm_st_chg_cause = 0;
2245+
2246+ /* explicitly commit state changes (even if it was already applied implicitly
2247+ * by some lb state change function), so we don't miss anything
2248+ */
2249+ srv_lb_commit_status(s);
2250+
2251+ /* check if server stats must be updated due the the server state change */
2252+ if (srv_prev_state != s->cur_state) {
2253+ if (srv_prev_state == SRV_ST_STOPPED) {
2254+ /* server was down and no longer is */
2255+ if (s->last_change < now.tv_sec) // ignore negative times
2256+ s->down_time += now.tv_sec - s->last_change;
2257+ }
2258+ s->last_change = now.tv_sec;
2259+ }
2260+
2261+ /* check if backend stats must be updated due to the server state change */
2262+ if (prev_srv_count && s->proxy->srv_bck == 0 && s->proxy->srv_act == 0)
2263+ set_backend_down(s->proxy); /* backend going down */
2264+ else if (!prev_srv_count && (s->proxy->srv_bck || s->proxy->srv_act)) {
2265+ /* backend was down and is back up again:
2266+ * no helper function, updating last_change and backend downtime stats
2267+ */
2268+ if (s->proxy->last_change < now.tv_sec) // ignore negative times
2269+ s->proxy->down_time += now.tv_sec - s->proxy->last_change;
2270+ s->proxy->last_change = now.tv_sec;
2271+ }
2272 }
2273
2274 struct task *srv_cleanup_toremove_connections(struct task *task, void *context, unsigned short state)
2275diff --git a/src/ssl_sock.c b/src/ssl_sock.c
2276index b33580f..ac1109f 100644
2277--- a/src/ssl_sock.c
2278+++ b/src/ssl_sock.c
2279@@ -1162,7 +1162,7 @@ int ssl_sock_ocsp_stapling_cbk(SSL *ssl, void *arg)
2280 if (!ocsp ||
2281 !ocsp->response.area ||
2282 !ocsp->response.data ||
2283- (ocsp->expire < now.tv_sec))
2284+ (ocsp->expire < date.tv_sec))
2285 return SSL_TLSEXT_ERR_NOACK;
2286
2287 ssl_buf = OPENSSL_malloc(ocsp->response.data);
2288diff --git a/src/stream_interface.c b/src/stream_interface.c
2289index 1adecf3..09e62d9 100644
2290--- a/src/stream_interface.c
2291+++ b/src/stream_interface.c
2292@@ -955,6 +955,22 @@ void si_sync_send(struct stream_interface *si)
2293 return;
2294
2295 si_cs_send(cs);
2296+
2297+ if (likely(oc->flags & CF_WRITE_ACTIVITY)) {
2298+ struct channel *ic = si_ic(si);
2299+
2300+ if (tick_isset(ic->rex) && !(si->flags & SI_FL_INDEP_STR)) {
2301+ /* Note: to prevent the client from expiring read timeouts
2302+ * during writes, we refresh it. We only do this if the
2303+ * interface is not configured for "independent streams",
2304+ * because for some applications it's better not to do this,
2305+ * for instance when continuously exchanging small amounts
2306+ * of data which can full the socket buffers long before a
2307+ * write timeout is detected.
2308+ */
2309+ ic->rex = tick_add_ifset(now_ms, ic->rto);
2310+ }
2311+ }
2312 }
2313
2314 /* Updates at once the channel flags, and timers of both stream interfaces of a
2315diff --git a/src/tcp_rules.c b/src/tcp_rules.c
2316index 229c102..2ac0234 100644
2317--- a/src/tcp_rules.c
2318+++ b/src/tcp_rules.c
2319@@ -123,7 +123,7 @@ int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit)
2320 * - if one rule returns KO, then return KO
2321 */
2322
2323- if ((req->flags & (CF_EOI|CF_SHUTR|CF_READ_ERROR)) || channel_full(req, global.tune.maxrewrite) ||
2324+ if ((req->flags & (CF_SHUTR|CF_READ_ERROR)) || channel_full(req, global.tune.maxrewrite) ||
2325 si_rx_blocked_room(chn_prod(req)) ||
2326 !s->be->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms))
2327 partial = SMP_OPT_FINAL;
2328@@ -301,7 +301,7 @@ int tcp_inspect_response(struct stream *s, struct channel *rep, int an_bit)
2329 * - if one rule returns OK, then return OK
2330 * - if one rule returns KO, then return KO
2331 */
2332- if ((rep->flags & (CF_EOI|CF_SHUTR|CF_READ_ERROR)) || channel_full(rep, global.tune.maxrewrite) ||
2333+ if ((rep->flags & (CF_SHUTR|CF_READ_ERROR)) || channel_full(rep, global.tune.maxrewrite) ||
2334 si_rx_blocked_room(chn_prod(rep)) ||
2335 !s->be->tcp_rep.inspect_delay || tick_is_expired(rep->analyse_exp, now_ms))
2336 partial = SMP_OPT_FINAL;
2337diff --git a/src/time.c b/src/time.c
2338index c2bdbdc..7d89008 100644
2339--- a/src/time.c
2340+++ b/src/time.c
2341@@ -26,7 +26,8 @@ THREAD_LOCAL unsigned int samp_time; /* total elapsed time over current
2342 THREAD_LOCAL unsigned int idle_time; /* total idle time over current sample */
2343 THREAD_LOCAL struct timeval now; /* internal date is a monotonic function of real clock */
2344 THREAD_LOCAL struct timeval date; /* the real current date */
2345-struct timeval start_date; /* the process's start date */
2346+struct timeval start_date; /* the process's start date */
2347+struct timeval ready_date; /* date when the process was considered ready */
2348 THREAD_LOCAL struct timeval before_poll; /* system date before calling poll() */
2349 THREAD_LOCAL struct timeval after_poll; /* system date after leaving poll() */
2350

Subscribers

People subscribed via source and target branches