From: Christopher Faulet Date: Wed, 23 Sep 2026 04:33:53 +0000 (+0000) Subject: BUG/MEDIUM: http-act: ignore "capture len" rules evaluated from a backend X-Git-Tag: v3.5-dev8~128 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=2161c6087926ef4e201978d7520acc001a672c80;p=haproxy.git BUG/MEDIUM: http-act: ignore "capture len" rules evaluated from a backend An "http-request capture len " rule creates a capture slot in the proxy it is declared in. The parser only refuses it in proxies without the frontend capability, so it is accepted in a "listen" section. But the capture slots of a stream are allocated from its frontend (s->req_cap is sized from fe->nb_req_cap), and released through the frontend's capture list with the frontend's pools. So when the listen section is also used as the backend of another frontend, the rule runs with an index taken from the listen's numbering on the slot array of the other frontend: - if this frontend has no capture, s->req_cap is NULL and the first request crashes the process; - otherwise the slot aliases one of the frontend's slots, it is allocated from the listen's pool and later released to the frontend's pool, which corrupts the pools. It may also be beyond the end of the slot array. The slot cannot exist for such streams, so the rule is now ignored when the proxy evaluating it is not the stream's frontend. This cannot be detected at parsing time since a backend may be resolved at run time. The documentation is updated and a reg-test is added. This must be backported to all supported versions. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 509825923..91e235f77 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -15780,8 +15780,11 @@ capture [ len | id ] sample fetch methods to feed it into headers or anything. The length should be limited given that this size will be allocated for each capture during the whole stream life. Note that the length is only usable with "http-request" - rules. Please check section 7.3 (Fetching samples), "capture request header" - and "capture response header" for more information. + rules. Such a slot only exists in the section which declares it, so when this + rule is placed in a "listen" section which is also used as the backend of + another frontend, it is ignored for the streams coming from that frontend. + Please check section 7.3 (Fetching samples), "capture request header" and + "capture response header" for more information. If the keyword "id" is used instead of "len", the action tries to store the captured string in a previously declared capture slot. This is useful to run diff --git a/reg-tests/http-rules/capture_len_listen_as_backend.vtc b/reg-tests/http-rules/capture_len_listen_as_backend.vtc new file mode 100644 index 000000000..3849daeb3 --- /dev/null +++ b/reg-tests/http-rules/capture_len_listen_as_backend.vtc @@ -0,0 +1,59 @@ +varnishtest "capture with a length in a listen section also used as a backend" + +# A "capture len " rule creates a capture slot in the section it +# is declared in. The capture slots of a stream are those of its frontend, +# so when the section is a "listen" which is also reached as the backend +# of another frontend, the slot does not exist for the streams coming from +# there and the rule must be ignored. Otherwise the rule writes into a slot +# array which may not exist, or into a slot which belongs to the frontend. +# +# The cases are: +# - the listen section reached directly: the capture works; +# - reached from a frontend which has no capture at all; +# - reached from a frontend which has its own capture, which must be +# left untouched. + +feature ignore_unknown_macro + +haproxy h1 -conf { + defaults + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend fe_nocap + bind "fd@${fe_nocap}" + use_backend lst + + frontend fe_cap + bind "fd@${fe_cap}" + capture request header x-a len 1 + use_backend lst + + listen lst + bind "fd@${lst}" + http-request capture req.hdr(x-b) len 40 + http-request return status 200 hdr cap0 "%[capture.req.hdr(0)]" +} -start + +client c1 -connect ${h1_lst_sock} { + txreq -hdr "x-b: hello" + rxresp + expect resp.status == 200 + expect resp.http.cap0 == "hello" +} -run + +client c2 -connect ${h1_fe_nocap_sock} { + txreq -hdr "x-b: hello" + rxresp + expect resp.status == 200 + expect resp.http.cap0 == "" +} -run + +client c3 -connect ${h1_fe_cap_sock} { + txreq -hdr "x-a: A" -hdr "x-b: hello" + rxresp + expect resp.status == 200 + expect resp.http.cap0 == "A" +} -run diff --git a/src/http_act.c b/src/http_act.c index fddd74dfe..653c8fc95 100644 --- a/src/http_act.c +++ b/src/http_act.c @@ -794,6 +794,14 @@ static enum act_return http_action_req_capture(struct act_rule *rule, struct pro char **cap = s->req_cap; int len; + /* The capture slot was declared in the proxy holding the rule, but the + * slots of the stream are those of its frontend. When the rule is + * evaluated from a listen section used as a backend, the slot does not + * exist, so the rule is ignored. + */ + if (px != strm_fe(s)) + return ACT_RET_CONT; + key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR); if (!key) return ACT_RET_CONT;