]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: http-fetch: reject a \0 byte in req.body_param
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 14 Sep 2026 09:39:50 +0000 (11:39 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Wed, 16 Sep 2026 07:18:10 +0000 (09:18 +0200)
A string sample cannot contain a \0 byte, but req.body_param returned
the raw bytes of the body, so a client could put a \0 in a parameter
value. Such a value was then matched by "-m str" on its prefix:
"public\0x" matched "public".

Fail the fetch when the value holds a \0 byte. The check is done in
smp_fetch_param(), also used by url_param, whose value cannot hold one
since the parsers reject it in the URI.

A reg-test is added to check that "u=public\0x" in a body does not
match "public".

This must be backported to all stable branches.

reg-tests/http-rules/body_param_embedded_nul.vtc [new file with mode: 0644]
src/http_fetch.c

diff --git a/reg-tests/http-rules/body_param_embedded_nul.vtc b/reg-tests/http-rules/body_param_embedded_nul.vtc
new file mode 100644 (file)
index 0000000..7a15e32
--- /dev/null
@@ -0,0 +1,41 @@
+varnishtest "req.body_param must not return a string with a \0 byte"
+feature ignore_unknown_macro
+
+# A string sample cannot contain a \0 byte. req.body_param returns the raw
+# bytes of the body, so a client can put a \0 in a value. Such a value must
+# not be matched by "-m str" on its prefix.
+
+haproxy h1 -conf {
+    global
+    .if feature(THREAD)
+        thread-groups 1
+    .endif
+
+    defaults
+        mode http
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    frontend fe1
+        bind "fd@${fe1}"
+        option http-buffer-request
+        http-request set-var-fmt(txn.v) match if { req.body_param(u) -m str public }
+        http-request return status 200 hdr verdict %[var(txn.v,nomatch)]
+} -start
+
+client c1 -connect ${h1_fe1_sock} {
+    # body "u=public"
+    txreq -req POST -url "/" -body "u=public"
+    rxresp
+    expect resp.status == 200
+    expect resp.http.verdict == "match"
+
+    # body "u=public\0x", sent as hex since a string cannot hold a \0 byte:
+    # "u=" "public" "\0" "x"
+    txreq -req POST -url "/" -hdr "Content-Length: 10"
+    sendhex "753d 7075626c6963 00 78"
+    rxresp
+    expect resp.status == 200
+    expect resp.http.verdict == "nomatch"
+} -run
index 6133983c16a763e9e06bbf5583273cd2e7b95f36..614d7336cf9619faa86412075d91930315678a2d 100644 (file)
@@ -2012,6 +2012,10 @@ static int smp_fetch_param(char delim, const char *name, int name_len, const str
                smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
        }
 
+       /* a string sample cannot contain a NUL byte */
+       if (memchr(smp->data.u.str.area, 0, smp->data.u.str.data))
+               return 0;
+
        /* Update context, check wrapping. */
        chunks[0] = vend;
        if (chunks[2] && vend >= chunks[2] && vend <= chunks[3]) {