]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: http-act: ignore "capture len" rules evaluated from a backend
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 23 Sep 2026 04:33:53 +0000 (04:33 +0000)
committerChristopher Faulet <cfaulet@haproxy.com>
Wed, 23 Sep 2026 12:05:25 +0000 (14:05 +0200)
An "http-request capture <expr> len <n>" 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.

doc/configuration.txt
reg-tests/http-rules/capture_len_listen_as_backend.vtc [new file with mode: 0644]
src/http_act.c

index 509825923be6506867ddf77d162693b9237a801e..91e235f77a91a888095fe534d90162cf652cca9d 100644 (file)
@@ -15780,8 +15780,11 @@ capture <sample> [ len <length> | id <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 (file)
index 0000000..3849dae
--- /dev/null
@@ -0,0 +1,59 @@
+varnishtest "capture with a length in a listen section also used as a backend"
+
+# A "capture <expr> len <n>" 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 == "<undef>"
+} -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
index fddd74dfe7d8c3131aa1ecc3fe7c5dc93302ea08..653c8fc95d45c38f5bc20ed0953c1ca1901e6cc7 100644 (file)
@@ -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;