From: Willy Tarreau Date: Mon, 27 Jul 2026 09:40:52 +0000 (+0200) Subject: BUG/MINOR: http-act: fix a double free of the regex on a rule parsing error X-Git-Tag: v3.5-dev4~106 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6a8b544178b927a456472c9b772fe8b3220bf42a;p=thirdparty%2Fhaproxy.git BUG/MINOR: http-act: fix a double free of the regex on a rule parsing error parse_replace_uri() and parse_http_replace_header() compile their regex into arg.http.re>, and when the log-format argument that follows fails to parse they release it before reporting the error: if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, ...)) { regex_free(rule->arg.http.re); return ACT_RET_PRS_ERR; } The pointer is left dangling in the rule while release_ptr> has already been set to release_http_action(), which does exactly the same: if (rule->arg.http.re) regex_free(rule->arg.http.re); On ACT_RET_PRS_ERR the caller (parse_http_req_cond() & friends) calls free_act_rule(), which invokes release_ptr, so regex_free() runs twice on the same object. It ends up calling regfree()/pcre*_free() on freed memory and free() on an already freed pointer. It is easily reproduced with: http-request replace-uri ^/foo /bar%[nosuchfetch] http-request replace-header X-Foo ^a b%[nosuchfetch] Both abort under MALLOC_CHECK_=3, and the second one even segfaults with the libc regex backend, so "haproxy -c" dies instead of reporting the configuration error (and the remaining errors of the file are never reported). This only happens on an invalid configuration during parsing, so it has no security impact, but a configuration checker must not crash. Let's reset the pointer after releasing it, as done for in release_act_http_reply(). This should be backported to all supported versions. --- diff --git a/src/http_act.c b/src/http_act.c index bf0d9a47c..eaa1148e2 100644 --- a/src/http_act.c +++ b/src/http_act.c @@ -635,6 +635,7 @@ static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, st cap |= SMP_VAL_BE_HRQ_HDR; if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) { regex_free(rule->arg.http.re); + rule->arg.http.re = NULL; /* release_http_action() would free it again */ return ACT_RET_PRS_ERR; } @@ -1803,6 +1804,7 @@ static enum act_parse_ret parse_http_replace_header(const char **args, int *orig if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) { istfree(&rule->arg.http.str); regex_free(rule->arg.http.re); + rule->arg.http.re = NULL; /* release_http_action() would free it again */ return ACT_RET_PRS_ERR; }