]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: http/config: add acceptable-http-re{quest,sponse}-violations
authorWilly Tarreau <w@1wt.eu>
Tue, 20 Oct 2026 13:03:21 +0000 (15:03 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 21 Oct 2026 15:40:55 +0000 (17:40 +0200)
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.

include/haproxy/global-t.h
include/haproxy/http-t.h
src/haproxy.c
src/http.c

index 6ebfaffb1badb4be8fbc58a71029b6010e86a71e..6f8220589cda3a0346925a38ded1da24ad16b2e9 100644 (file)
@@ -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 */
index 7c6493cdfd41b77dee164b8d52a489ffb427189f..4f392d72ff885d0ff42b36f0fcf47c3215bbac4c 100644 (file)
@@ -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 */
 
 /*
index a630688667425a71c096571fecfd6b02d1e12623..046b289a134cb7136b019655259838ef2dd8b93d 100644 (file)
@@ -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,
index 9cc202475b29a6247bbb923afbfc90caa17e2198..d67e4015f4749e2960c3b4c611527524cbad223d 100644 (file)
@@ -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 <b> */
+               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 */ }
 }};