]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: http-act: fix a double free of the regex on a rule parsing error
authorWilly Tarreau <w@1wt.eu>
Mon, 27 Jul 2026 09:40:52 +0000 (11:40 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Jul 2026 13:30:43 +0000 (15:30 +0200)
parse_replace_uri() and parse_http_replace_header() compile their regex into
<rule->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 <rule->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 <arg.http_reply> in
release_act_http_reply().

This should be backported to all supported versions.

src/http_act.c

index bf0d9a47c62d4390170f55cf6cf5ccebcd237d2f..eaa1148e257fa2de5540e0b598199c524ba77d9e 100644 (file)
@@ -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;
        }