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.
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;
}
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;
}