]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: sample: reject a \0 byte in url_dec, json_query and jwt_*_query
authorFrederic Lecaille <flecaille@haproxy.com>
Fri, 11 Sep 2026 13:49:19 +0000 (15:49 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Fri, 11 Sep 2026 13:52:40 +0000 (15:52 +0200)
A string sample cannot contain a \0 byte by design: this is what
distinguishes it from a binary sample, and every consumer relies on it.
Yet url_dec decoded "%00" to a \0, json_query decoded "\u0000" to one,
and jwt_header_query/jwt_payload_query could return one from the base64
decoded JSON. Such a sample was then matched by "-m str" on its prefix
only, so "/public%00/admin" matched "/public" and an allow-list could be
bypassed.

Commit 442f583f4 ("BUG/MINOR: pattern: do not match a string with an
embedded \0 on its prefix") worked around it in pat_match_str(), which
is the wrong place since such a string must not exist at all.

This commit reverts it and fixes the producers instead: url_decode() now
fails on "%00" like it does on an invalid sequence, and json_query and
jwt_*_query fail on a decoded \0. This is also cheaper, as the string
is checked once where it is produced instead of on every call to
pat_match_str().

This must be backported to all stable branches, in place of 442f583f4
where it was not yet backported.

src/pattern.c
src/sample.c
src/tools.c

index 656ffeade2650210d6c340ff6cad1f2ee2024901..f4ed2791d7ecc62a11f346b61e07b5034b0da406 100644 (file)
@@ -499,13 +499,6 @@ struct pattern *pat_match_str(struct sample *smp, struct pattern_expr *expr, int
        struct pattern *ret = NULL;
        struct lru64 *lru = NULL;
 
-       /* Patterns never contain a NUL byte, so a sample with an embedded
-        * NUL cannot be an exact match. Without this check, the tree lookup
-        * would stop at the NUL and match on the prefix only.
-        */
-       if (memchr(smp->data.u.str.area, 0, smp->data.u.str.data))
-               return NULL;
-
        /* Lookup a string in the expression's pattern tree. */
        if (!eb_is_empty(&expr->pattern_tree)) {
                if (!pat_match_ensure_str(smp))
index 59c14b71ca72e16572a1764c9aa1a5673980a257..9d29baa57f97c1f279f63b2a39ae94d9a60e8e84 100644 (file)
@@ -4703,6 +4703,10 @@ static int sample_conv_json_query(const struct arg *args, struct sample *smp, vo
                                return 0;
                        }
 
+                       /* mjson might return an embedded \0 that is not valid as a string */
+                       if (memchr(trash->area, 0, len))
+                               return 0;
+
                        trash->data = len;
                        smp->data.u.str = *trash;
                        smp->data.type = SMP_T_STR;
@@ -4967,6 +4971,10 @@ static int sample_conv_jwt_member_query(const struct arg *args, struct sample *s
        if (ret == -1)
                goto end;
 
+       /* mjson might return an embedded \0 that is not valid as a string */
+       if (memchr(decoded_header->area, 0, ret))
+               goto end;
+
        decoded_header->data = ret;
        if (args[0].type != ARGT_STR) {
                smp->data.u.str = *decoded_header;
index 186e3a1449894f2927f886fb569436df9ab521fe..62fd2bdcf2111aebf2344f07f9dc6c2c2ecec0bd 100644 (file)
@@ -2654,7 +2654,11 @@ int url_decode(char *string, int in_form)
                case '%' :
                        if (!ishex(in[1]) || !ishex(in[2]))
                                goto end;
-                       *out++ = (hex2i(in[1]) << 4) + hex2i(in[2]);
+                       *out = (hex2i(in[1]) << 4) + hex2i(in[2]);
+                       /* forbid %00 which cannot be represented in a string */
+                       if (!*out)
+                               goto end;
+                       out++;
                        in += 2;
                        break;
                case '?':