From: Frederic Lecaille Date: Fri, 11 Sep 2026 13:49:19 +0000 (+0200) Subject: BUG/MINOR: sample: reject a \0 byte in url_dec, json_query and jwt_*_query X-Git-Tag: v3.5-dev7~20 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=37204a71ac6eea761fe01aed96b3cd340a0ceda4;p=haproxy.git BUG/MINOR: sample: reject a \0 byte in url_dec, json_query and jwt_*_query 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. --- diff --git a/src/pattern.c b/src/pattern.c index 656ffeade..f4ed2791d 100644 --- a/src/pattern.c +++ b/src/pattern.c @@ -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)) diff --git a/src/sample.c b/src/sample.c index 59c14b71c..9d29baa57 100644 --- a/src/sample.c +++ b/src/sample.c @@ -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; diff --git a/src/tools.c b/src/tools.c index 186e3a144..62fd2bdcf 100644 --- a/src/tools.c +++ b/src/tools.c @@ -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 '?':