From: Willy Tarreau Date: Tue, 20 Oct 2026 13:03:21 +0000 (+0200) Subject: MINOR: http/config: add acceptable-http-re{quest,sponse}-violations X-Git-Tag: v3.5-dev10~87 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=3bca22650dddfe3992e10410f889e8b1ad537dee;p=haproxy.git MINOR: http/config: add acceptable-http-re{quest,sponse}-violations Proxies support HTTP violations relaxation in requests via option accept-unsafe-violations-in-http-request and in responses via option accept-unsafe-violations-in-http-response. The problem is that over time the list of exceptions has grown quite a bit, and that a simple application that requires to relax one tiny non-dangerous feature such as enabling raw utf-8 in URLs, we end up enabling many other things that can be much more dangerous. This patch introduces a new pair of global settings that permit to declare which of the HTTP violations are acceptable when a proxy wants to use them. It's expected that such violations are rare enough so that a single global setting to adjust them all process- wide is sufficient. What the code does is that it introduces a bit field for the request and the response in the global section, with each bit indicating that one violation is acceptable, and it permits via the global keyword to replace the whole list, add an entry or remove an entry. By default, for backwards compatibility, all exceptions are allowed, but it's expected that most of the time users will just set one, or will keep the defaults except some of the most dangerous ones. For now only the declaration and parsing are handled, and only "none" and "all" are supported. The keyword parser supports fuzzy matching and will propose approximating names when one is misspelled. Subsequent patches will add the options themselves. --- diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h index 6ebfaffb1..6f8220589 100644 --- a/include/haproxy/global-t.h +++ b/include/haproxy/global-t.h @@ -159,6 +159,8 @@ struct global { int spread_checks; int max_spread_checks; int max_syslog_len; + uint acc_hreq_vio; + uint acc_hres_vio; char *chroot; char *pidfile; char *node, *desc; /* node name & description */ diff --git a/include/haproxy/http-t.h b/include/haproxy/http-t.h index 7c6493cdf..4f392d72f 100644 --- a/include/haproxy/http-t.h +++ b/include/haproxy/http-t.h @@ -198,6 +198,29 @@ struct http_uri_parser { enum http_uri_parser_format format; /* rfc 7230 5.3 HTTP URI format */ }; +/* supported HTTP directions for various processings */ +enum http_dir { + HTTP_DIR_NONE = 0, // never applies + HTTP_DIR_REQ = 1, // applies to request + HTTP_DIR_RES = 2, // applies to response + HTTP_DIR_BOTH = HTTP_DIR_REQ | HTTP_DIR_RES, // applies to both +}; + +/* known configurable HTTP violations */ +enum http_violation_idx { + /* insert new violations above this line */ + HTTP_VIO_TOTAL_COUNT = 32, // end marker + HTTP_VIO_NONE, // special code for none + HTTP_VIO_ALL // special code for all +}; + +struct http_violation { + const char *name; + enum http_violation_idx index; + enum http_dir dir; +}; + + #endif /* _HAPROXY_HTTP_T_H */ /* diff --git a/src/haproxy.c b/src/haproxy.c index a63068866..046b289a1 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -172,6 +172,8 @@ struct global global = { .maxzlibmem = DEFAULT_MAXZLIBMEM * 1024U * 1024U, .comp_rate_lim = 0, .ssl_server_verify = SSL_SERVER_VERIFY_REQUIRED, + .acc_hreq_vio = ~0U, // all by default + .acc_hres_vio = ~0U, // all by default .unix_bind = { .ux = { .uid = -1, diff --git a/src/http.c b/src/http.c index 9cc202475..d67e4015f 100644 --- a/src/http.c +++ b/src/http.c @@ -1627,6 +1627,127 @@ struct ist http_trim_trailing_spht(struct ist value) return ret; } +/* list of known HTTP configurable violations */ +const struct http_violation http_violations[] = { + { "none", HTTP_VIO_NONE, HTTP_DIR_BOTH }, + /* insert new violations below this line */ + /* insert new violations above this line */ + { "all", HTTP_VIO_ALL, HTTP_DIR_BOTH }, + { NULL, 0 } +}; + +/* parses a global "acceptable-http-request-violations" and + * "acceptable-http-request-violations" directive. + */ +static int http_parse_acceptable_http_violations(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + char *cmd = args[0]; + char *p, *b, *e; + size_t idx = 0; + uint *target, bits; + enum http_dir dir; + int queued = 0; + int ret = -1; + int match; + int op; + + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(cmd, "acceptable-http-request-violations") == 0) { + dir = HTTP_DIR_REQ; + target = &global.acc_hreq_vio; + } + else if (strcmp(cmd, "acceptable-http-response-violations") == 0) { + dir = HTTP_DIR_RES; + target = &global.acc_hres_vio; + } + else + ABORT_NOW(); + + p = args[1]; + if (!*p) { + memprintf(err, "Missing violation names for '%s'.", cmd); + goto end; + } + + e = p + strlen(p); + + /* operation: <0 = remove, 0 = replace, >0 = add. */ + op = 0; // assignment by default + bits = 0; + queued = 0; + + while (*p) { + if (*p == '+' || *p == '-') { + /* must first commit what we already have */ + goto commit; + } + + /* not an operator, thus real keyword */ + b = p; + p = strchr(p, ','); + if (p) + *(p++) = 0; // points to next word + else + p = e; // points to trailing zero + + /* we have a delimited word at */ + match = find_word_index_in_array(http_violations, sizeof(http_violations[0]), -1, + offsetof(struct http_violation, name), b, &idx); + if (match != 2) { + memprintf(err, "Invalid http-violation type '%s' in '%s' at position %lu.", + b, cmd, (ulong)(b - *args)); + if (match == 1) + memprintf(err, "%s Maybe you meant '%s' ?", *err, http_violations[idx].name); + goto end; + } + + /* OK the keyword exists */ + if (!(http_violations[idx].dir & dir)) { + memprintf(err, "http-violation '%s' is not compatible with '%s'.", + b, cmd); + goto end; + } + + /* set relevant bit(s) */ + if (http_violations[idx].index == HTTP_VIO_NONE) + bits = 0; + else if (http_violations[idx].index == HTTP_VIO_ALL) + bits = ~0U; + else + bits |= 1U << http_violations[idx].index; + queued++; + + /* do not commit yet */ + if (*p) + continue; + commit: + if (queued) { + /* end reached or new operator, must commit */ + if (op < 0) + *target &= ~bits; + else if (op > 0) + *target |= bits; + else + *target = bits; + bits = 0; + queued = 0; + } + + /* now we can change the operator if needed */ + if (*p == '+' || *p == '-') { + op = (*p == '+') ? 1 : -1; + p++; + } + } + ret = 0; + end: + return ret; +} + /* initialize the required structures and arrays */ static void _http_init() { @@ -1730,6 +1851,8 @@ static int http_parse_http_err_fail_codes(char **args, int section_type, struct static struct cfg_kw_list cfg_kws = {{ },{ { CFG_GLOBAL, "http-err-codes", http_parse_http_err_fail_codes }, { CFG_GLOBAL, "http-fail-codes", http_parse_http_err_fail_codes }, + { CFG_GLOBAL, "acceptable-http-request-violations", http_parse_acceptable_http_violations }, + { CFG_GLOBAL, "acceptable-http-response-violations", http_parse_acceptable_http_violations }, { /* END */ } }};