diff --git a/SOURCES/httpd-2.4.57-CVE-2024-38476.patch b/SOURCES/httpd-2.4.57-CVE-2024-38476.patch new file mode 100644 index 0000000..479b4c3 --- /dev/null +++ b/SOURCES/httpd-2.4.57-CVE-2024-38476.patch @@ -0,0 +1,303 @@ +From 554554b0ebb14d6578adb70a389c57a0d5f18a3b Mon Sep 17 00:00:00 2001 +From: Eric Covener +Date: Mon, 24 Jun 2024 17:54:34 +0000 +Subject: [PATCH] Merge r1918551 from trunk: + +add ap_set_content_type_ex to differentiate + +trusted sources + + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1918560 13f79535-47bb-0310-9956-ffa450edef68 +--- + include/http_protocol.h | 11 +++++++++++ + include/httpd.h | 7 +++++++ + modules/http/http_protocol.c | 6 ++++++ + modules/http/mod_mime.c | 20 ++++++++++---------- + modules/mappers/mod_actions.c | 6 ++++-- + modules/mappers/mod_negotiation.c | 8 ++++---- + modules/mappers/mod_rewrite.c | 2 +- + modules/metadata/mod_headers.c | 6 +++--- + modules/metadata/mod_mime_magic.c | 4 ++-- + server/config.c | 2 +- + server/core.c | 2 +- + 11 files changed, 50 insertions(+), 24 deletions(-) + +diff --git a/include/http_protocol.h b/include/http_protocol.h +index 94c481e5f43..f2c99c9e86e 100644 +--- a/include/http_protocol.h ++++ b/include/http_protocol.h +@@ -438,6 +438,17 @@ AP_DECLARE(void) ap_clear_method_list(ap_method_list_t *l); + */ + AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct); + ++/** ++ * Set the content type for this request (r->content_type). ++ * @param r The current request ++ * @param ct The new content type ++ * @param trusted If non-zero, The content-type should come from a ++ * trusted source such as server configuration rather ++ * than application output. ++ * for the AddOutputFilterByType directive to work correctly. ++ */ ++AP_DECLARE(void) ap_set_content_type_ex(request_rec *r, const char *ct, int trusted); ++ + /** + * Set the Accept-Ranges header for this response + * @param r The current request +diff --git a/include/httpd.h b/include/httpd.h +index 826c46ef591..766df2bde00 100644 +--- a/include/httpd.h ++++ b/include/httpd.h +@@ -667,6 +667,7 @@ typedef apr_uint64_t ap_request_bnotes_t; + * + */ + #define AP_REQUEST_STRONG_ETAG 1 >> 0 ++#define AP_REQUEST_TRUSTED_CT 1 << 1 + + /** + * This is a convenience macro to ease with getting specific request +@@ -689,6 +690,12 @@ typedef apr_uint64_t ap_request_bnotes_t; + AP_REQUEST_GET_BNOTE((r), AP_REQUEST_STRONG_ETAG) + /** @} */ + ++/** ++ * Returns true if the content-type field is from a trusted source ++ */ ++#define AP_REQUEST_IS_TRUSTED_CT(r) \ ++ (!!AP_REQUEST_GET_BNOTE((r), AP_REQUEST_TRUSTED_CT)) ++/** @} */ + + /** + * @defgroup module_magic Module Magic mime types +diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c +index d031f245188..c31e8737337 100644 +--- a/modules/http/http_protocol.c ++++ b/modules/http/http_protocol.c +@@ -1097,8 +1097,14 @@ AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct) + } + else if (!r->content_type || strcmp(r->content_type, ct)) { + r->content_type = ct; ++ AP_REQUEST_SET_BNOTE(r, AP_REQUEST_TRUSTED_CT, 0); + } + } ++AP_DECLARE(void) ap_set_content_type_ex(request_rec *r, const char *ct, int trusted) ++{ ++ ap_set_content_type(r, ct); ++ AP_REQUEST_SET_BNOTE(r, AP_REQUEST_TRUSTED_CT, trusted ? AP_REQUEST_TRUSTED_CT : 0); ++} + + AP_DECLARE(void) ap_set_accept_ranges(request_rec *r) + { +diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c +index 700f824f32a..51095a0e74c 100644 +--- a/modules/http/mod_mime.c ++++ b/modules/http/mod_mime.c +@@ -759,7 +759,7 @@ static int find_ct(request_rec *r) + int found_metadata = 0; + + if (r->finfo.filetype == APR_DIR) { +- ap_set_content_type(r, DIR_MAGIC_TYPE); ++ ap_set_content_type_ex(r, DIR_MAGIC_TYPE, 1); + return OK; + } + +@@ -850,7 +850,7 @@ static int find_ct(request_rec *r) + if (exinfo == NULL || !exinfo->forced_type) { + if ((type = apr_hash_get(mime_type_extensions, ext, + APR_HASH_KEY_STRING)) != NULL) { +- ap_set_content_type(r, (char*) type); ++ ap_set_content_type_ex(r, (char*) type, 1); + found = 1; + } + } +@@ -859,7 +859,7 @@ static int find_ct(request_rec *r) + + /* empty string is treated as special case for RemoveType */ + if (exinfo->forced_type && *exinfo->forced_type) { +- ap_set_content_type(r, exinfo->forced_type); ++ ap_set_content_type_ex(r, exinfo->forced_type, 1); + found = 1; + } + +@@ -964,33 +964,33 @@ static int find_ct(request_rec *r) + memcpy(tmp, ctp->subtype, ctp->subtype_len); + tmp += ctp->subtype_len; + *tmp = 0; +- ap_set_content_type(r, base_content_type); ++ ap_set_content_type_ex(r, base_content_type, AP_REQUEST_IS_TRUSTED_CT(r)); + while (pp != NULL) { + if (charset && !strcmp(pp->attr, "charset")) { + if (!override) { +- ap_set_content_type(r, ++ ap_set_content_type_ex(r, + apr_pstrcat(r->pool, + r->content_type, + "; charset=", + charset, +- NULL)); ++ NULL), AP_REQUEST_IS_TRUSTED_CT(r)); + override = 1; + } + } + else { +- ap_set_content_type(r, ++ ap_set_content_type_ex(r, + apr_pstrcat(r->pool, + r->content_type, + "; ", pp->attr, + "=", pp->val, +- NULL)); ++ NULL), AP_REQUEST_IS_TRUSTED_CT(r)); + } + pp = pp->next; + } + if (charset && !override) { +- ap_set_content_type(r, apr_pstrcat(r->pool, r->content_type, ++ ap_set_content_type_ex(r, apr_pstrcat(r->pool, r->content_type, + "; charset=", charset, +- NULL)); ++ NULL), AP_REQUEST_IS_TRUSTED_CT(r)); + } + } + } +diff --git a/modules/mappers/mod_actions.c b/modules/mappers/mod_actions.c +index ac9c3b7428f..5e398b53d9e 100644 +--- a/modules/mappers/mod_actions.c ++++ b/modules/mappers/mod_actions.c +@@ -182,8 +182,10 @@ static int action_handler(request_rec *r) + return DECLINED; + + /* Second, check for actions (which override the method scripts) */ +- action = r->handler ? r->handler : +- ap_field_noparam(r->pool, r->content_type); ++ action = r->handler; ++ if (!action && AP_REQUEST_IS_TRUSTED_CT(r)) { ++ action = ap_field_noparam(r->pool, r->content_type); ++ } + + if (action && (t = apr_table_get(conf->action_types, action))) { + int virtual = (*t++ == '0' ? 0 : 1); +diff --git a/modules/mappers/mod_negotiation.c b/modules/mappers/mod_negotiation.c +index c056b284550..a528f814397 100644 +--- a/modules/mappers/mod_negotiation.c ++++ b/modules/mappers/mod_negotiation.c +@@ -1167,7 +1167,7 @@ static int read_types_multi(negotiation_state *neg) + * might be doing. + */ + if (sub_req->handler && !sub_req->content_type) { +- ap_set_content_type(sub_req, CGI_MAGIC_TYPE); ++ ap_set_content_type_ex(sub_req, CGI_MAGIC_TYPE, 1); + } + + /* +@@ -3003,14 +3003,14 @@ static int handle_map_file(request_rec *r) + /* set MIME type and charset as negotiated */ + if (best->mime_type && *best->mime_type) { + if (best->content_charset && *best->content_charset) { +- ap_set_content_type(r, apr_pstrcat(r->pool, ++ ap_set_content_type_ex(r, apr_pstrcat(r->pool, + best->mime_type, + "; charset=", + best->content_charset, +- NULL)); ++ NULL), 1); + } + else { +- ap_set_content_type(r, apr_pstrdup(r->pool, best->mime_type)); ++ ap_set_content_type_ex(r, apr_pstrdup(r->pool, best->mime_type), 1); + } + } + +diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c +index bbcc11b3c52..df6f16b83f0 100644 +--- a/modules/mappers/mod_rewrite.c ++++ b/modules/mappers/mod_rewrite.c +@@ -5333,7 +5333,7 @@ static int hook_mimetype(request_rec *r) + rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'", + r->filename, t)); + +- ap_set_content_type(r, t); ++ ap_set_content_type_ex(r, t, 1); + } + + /* handler */ +diff --git a/modules/metadata/mod_headers.c b/modules/metadata/mod_headers.c +index ef812cd3edc..4838bd6cd0d 100644 +--- a/modules/metadata/mod_headers.c ++++ b/modules/metadata/mod_headers.c +@@ -783,14 +783,14 @@ static int do_headers_fixup(request_rec *r, apr_table_t *headers, + break; + case hdr_set: + if (!ap_cstr_casecmp(hdr->header, "Content-Type")) { +- ap_set_content_type(r, process_tags(hdr, r)); ++ ap_set_content_type_ex(r, process_tags(hdr, r), 1); + } + apr_table_setn(headers, hdr->header, process_tags(hdr, r)); + break; + case hdr_setifempty: + if (NULL == apr_table_get(headers, hdr->header)) { + if (!ap_cstr_casecmp(hdr->header, "Content-Type")) { +- ap_set_content_type(r, process_tags(hdr, r)); ++ ap_set_content_type_ex(r, process_tags(hdr, r), 1); + } + apr_table_setn(headers, hdr->header, process_tags(hdr, r)); + } +@@ -809,7 +809,7 @@ static int do_headers_fixup(request_rec *r, apr_table_t *headers, + const char *repl = process_regexp(hdr, r->content_type, r); + if (repl == NULL) + return 0; +- ap_set_content_type(r, repl); ++ ap_set_content_type_ex(r, repl, 1); + } + if (apr_table_get(headers, hdr->header)) { + edit_do ed; +diff --git a/modules/metadata/mod_mime_magic.c b/modules/metadata/mod_mime_magic.c +index 7dac4fdbd3d..1c96db4cd7a 100644 +--- a/modules/metadata/mod_mime_magic.c ++++ b/modules/metadata/mod_mime_magic.c +@@ -788,7 +788,7 @@ static int magic_rsl_to_request(request_rec *r) + /* XXX: this could be done at config time I'm sure... but I'm + * confused by all this magic_rsl stuff. -djg */ + ap_content_type_tolower(tmp); +- ap_set_content_type(r, tmp); ++ ap_set_content_type_ex(r, tmp, 1); + + if (state == rsl_encoding) { + tmp = rsl_strdup(r, encoding_frag, +@@ -2326,7 +2326,7 @@ static int revision_suffix(request_rec *r) + + /* extract content type/encoding/language from sub-request */ + if (sub->content_type) { +- ap_set_content_type(r, apr_pstrdup(r->pool, sub->content_type)); ++ ap_set_content_type_ex(r, apr_pstrdup(r->pool, sub->content_type), 1); + #if MIME_MAGIC_DEBUG + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01557) + MODNAME ": subrequest %s got %s", +diff --git a/server/config.c b/server/config.c +index 3d11ff58a44..635b65def1d 100644 +--- a/server/config.c ++++ b/server/config.c +@@ -418,7 +418,7 @@ AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r) + } + + if (!r->handler) { +- if (r->content_type) { ++ if (r->content_type && AP_REQUEST_IS_TRUSTED_CT(r)) { + handler = r->content_type; + if ((p=ap_strchr_c(handler, ';')) != NULL) { + char *new_handler = (char *)apr_pmemdup(r->pool, handler, +diff --git a/server/core.c b/server/core.c +index f511bba4897..843b97320f8 100644 +--- a/server/core.c ++++ b/server/core.c +@@ -4835,7 +4835,7 @@ static int core_override_type(request_rec *r) + /* Check for overrides with ForceType / SetHandler + */ + if (conf->mime_type && strcmp(conf->mime_type, "none")) +- ap_set_content_type(r, (char*) conf->mime_type); ++ ap_set_content_type_ex(r, (char*) conf->mime_type, 1); + + if (conf->expr_handler) { + const char *err; diff --git a/SOURCES/httpd-2.4.57-CVE-2024-39884+.patch b/SOURCES/httpd-2.4.57-CVE-2024-39884+.patch new file mode 100644 index 0000000..d64ba5f --- /dev/null +++ b/SOURCES/httpd-2.4.57-CVE-2024-39884+.patch @@ -0,0 +1,269 @@ + +-- Contains also regression fix (CVE-2024-40725) - https://svn.apache.org/viewvc?view=revision&revision=1919249 + +diff --git a/modules/cluster/mod_heartmonitor.c b/modules/cluster/mod_heartmonitor.c +index 53b6504..68db585 100644 +--- a/modules/cluster/mod_heartmonitor.c ++++ b/modules/cluster/mod_heartmonitor.c +@@ -782,7 +782,7 @@ static int hm_handler(request_rec *r) + hmserver.seen = apr_time_now(); + hm_update_stat(ctx, &hmserver, r->pool); + +- ap_set_content_type(r, "text/plain"); ++ ap_set_content_type_ex(r, "text/plain", 1); + ap_set_content_length(r, 2); + ap_rputs("OK", r); + ap_rflush(r); +diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c +index dea3f18..7a3eed7 100644 +--- a/modules/dav/main/mod_dav.c ++++ b/modules/dav/main/mod_dav.c +@@ -355,7 +355,7 @@ static int dav_error_response(request_rec *r, int status, const char *body) + r->status = status; + r->status_line = ap_get_status_line(status); + +- ap_set_content_type(r, "text/html; charset=ISO-8859-1"); ++ ap_set_content_type_ex(r, "text/html; charset=ISO-8859-1", 1); + + /* begin the response now... */ + ap_rvputs(r, +@@ -386,7 +386,7 @@ static int dav_error_response_tag(request_rec *r, + { + r->status = err->status; + +- ap_set_content_type(r, DAV_XML_CONTENT_TYPE); ++ ap_set_content_type_ex(r, DAV_XML_CONTENT_TYPE, 1); + + ap_rputs(DAV_XML_HEADER DEBUG_CR + "status = status; +- ap_set_content_type(r, DAV_XML_CONTENT_TYPE); ++ ap_set_content_type_ex(r, DAV_XML_CONTENT_TYPE, 1); + + /* Send the headers and actual multistatus response now... */ + ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR +@@ -2016,7 +2016,7 @@ static int dav_method_options(request_rec *r) + + /* send the options response */ + r->status = HTTP_OK; +- ap_set_content_type(r, DAV_XML_CONTENT_TYPE); ++ ap_set_content_type_ex(r, DAV_XML_CONTENT_TYPE, 1); + + /* send the headers and response body */ + ap_rputs(DAV_XML_HEADER DEBUG_CR +@@ -3328,7 +3328,7 @@ static int dav_method_lock(request_rec *r) + (*locks_hooks->close_lockdb)(lockdb); + + r->status = HTTP_OK; +- ap_set_content_type(r, DAV_XML_CONTENT_TYPE); ++ ap_set_content_type_ex(r, DAV_XML_CONTENT_TYPE, 1); + + ap_rputs(DAV_XML_HEADER DEBUG_CR "" DEBUG_CR, r); + if (lock == NULL) +diff --git a/modules/examples/mod_example_hooks.c b/modules/examples/mod_example_hooks.c +index f7ef5a5..d937906 100644 +--- a/modules/examples/mod_example_hooks.c ++++ b/modules/examples/mod_example_hooks.c +@@ -993,7 +993,7 @@ static int x_handler(request_rec *r) + * Set the Content-type header. Note that we do not actually have to send + * the headers: this is done by the http core. + */ +- ap_set_content_type(r, "text/html"); ++ ap_set_content_type_ex(r, "text/html", 1); + /* + * If we're only supposed to send header information (HEAD request), we're + * already there. +diff --git a/modules/filters/mod_data.c b/modules/filters/mod_data.c +index ddadd1b..4e6e636 100644 +--- a/modules/filters/mod_data.c ++++ b/modules/filters/mod_data.c +@@ -117,7 +117,7 @@ static apr_status_t data_out_filter(ap_filter_t *f, apr_bucket_brigade *bb) + } + } + +- ap_set_content_type(r, "text/plain"); ++ ap_set_content_type_ex(r, "text/plain", 1); + + } + +diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c +index 584d8fb..2c0cc67 100644 +--- a/modules/filters/mod_include.c ++++ b/modules/filters/mod_include.c +@@ -3972,7 +3972,7 @@ static int include_fixup(request_rec *r) + if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) + { + if (!r->content_type || !*r->content_type) { +- ap_set_content_type(r, "text/html"); ++ ap_set_content_type_ex(r, "text/html", 1); + } + r->handler = "default-handler"; + } +diff --git a/modules/filters/mod_proxy_html.c b/modules/filters/mod_proxy_html.c +index 7783da1..4205a61 100644 +--- a/modules/filters/mod_proxy_html.c ++++ b/modules/filters/mod_proxy_html.c +@@ -952,7 +952,7 @@ static apr_status_t proxy_html_filter(ap_filter_t *f, apr_bucket_brigade *bb) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01422) + "No i18n support found. Install mod_xml2enc if required"); + enc = XML_CHAR_ENCODING_NONE; +- ap_set_content_type(f->r, "text/html;charset=utf-8"); ++ ap_set_content_type_ex(f->r, "text/html;charset=utf-8", 1); + } + else { + /* if we wanted a non-default charset_out, insert the +@@ -968,7 +968,7 @@ static apr_status_t proxy_html_filter(ap_filter_t *f, apr_bucket_brigade *bb) + cenc, NULL)); + } + else /* Normal case, everything worked, utf-8 output */ +- ap_set_content_type(f->r, "text/html;charset=utf-8"); ++ ap_set_content_type_ex(f->r, "text/html;charset=utf-8", 1); + } + + ap_fputs(f->next, ctxt->bb, ctxt->cfg->doctype); +diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c +index ecca381..b486dfe 100644 +--- a/modules/generators/mod_cgi.c ++++ b/modules/generators/mod_cgi.c +@@ -795,7 +795,7 @@ static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f, + /* Force sub_req to be treated as a CGI request, even if ordinary + * typing rules would have called it something else. + */ +- ap_set_content_type(rr, CGI_MAGIC_TYPE); ++ ap_set_content_type_ex(rr, CGI_MAGIC_TYPE, 1); + + /* Run it. */ + rr_status = ap_run_sub_req(rr); +diff --git a/modules/generators/mod_cgid.c b/modules/generators/mod_cgid.c +index dd91b99..ce744bc 100644 +--- a/modules/generators/mod_cgid.c ++++ b/modules/generators/mod_cgid.c +@@ -1802,7 +1802,7 @@ static apr_status_t include_cgi(include_ctx_t *ctx, ap_filter_t *f, + /* Force sub_req to be treated as a CGI request, even if ordinary + * typing rules would have called it something else. + */ +- ap_set_content_type(rr, CGI_MAGIC_TYPE); ++ ap_set_content_type_ex(rr, CGI_MAGIC_TYPE, 1); + + /* Run it. */ + rr_status = ap_run_sub_req(rr); +diff --git a/modules/generators/mod_info.c b/modules/generators/mod_info.c +index 1662242..a94e4e4 100644 +--- a/modules/generators/mod_info.c ++++ b/modules/generators/mod_info.c +@@ -784,7 +784,7 @@ static int display_info(request_rec * r) + return DECLINED; + } + +- ap_set_content_type(r, "text/html; charset=ISO-8859-1"); ++ ap_set_content_type_ex(r, "text/html; charset=ISO-8859-1", 1); + + ap_rputs(DOCTYPE_XHTML_1_0T + "\n" +diff --git a/modules/generators/mod_status.c b/modules/generators/mod_status.c +index 5bada07..2cb38c7 100644 +--- a/modules/generators/mod_status.c ++++ b/modules/generators/mod_status.c +@@ -273,7 +273,7 @@ static int status_handler(request_rec *r) + if (r->method_number != M_GET) + return DECLINED; + +- ap_set_content_type(r, "text/html; charset=ISO-8859-1"); ++ ap_set_content_type_ex(r, "text/html; charset=ISO-8859-1", 1); + + /* + * Simple table-driven form data set parser that lets you alter the header +@@ -301,7 +301,7 @@ static int status_handler(request_rec *r) + no_table_report = 1; + break; + case STAT_OPT_AUTO: +- ap_set_content_type(r, "text/plain; charset=ISO-8859-1"); ++ ap_set_content_type_ex(r, "text/plain; charset=ISO-8859-1", 1); + short_report = 1; + break; + } +diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c +index 1a8df34..99ccbbb 100644 +--- a/modules/http/http_filters.c ++++ b/modules/http/http_filters.c +@@ -1249,7 +1249,7 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r) + } + } + +- ap_set_content_type(r, "message/http"); ++ ap_set_content_type_ex(r, "message/http", 1); + + /* Now we recreate the request, and echo it back */ + +diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c +index c31e873..3bc666e 100644 +--- a/modules/http/http_protocol.c ++++ b/modules/http/http_protocol.c +@@ -1443,10 +1443,10 @@ AP_DECLARE(void) ap_send_error_response(request_rec *r, int recursive_error) + request_conf->suppress_charset = 1; /* avoid adding default + * charset later + */ +- ap_set_content_type(r, "text/html"); ++ ap_set_content_type_ex(r, "text/html", 1); + } + else { +- ap_set_content_type(r, "text/html; charset=iso-8859-1"); ++ ap_set_content_type_ex(r, "text/html; charset=iso-8859-1", 1); + } + + if ((status == HTTP_METHOD_NOT_ALLOWED) +diff --git a/modules/http/http_request.c b/modules/http/http_request.c +index d59cfe2..7e9477b 100644 +--- a/modules/http/http_request.c ++++ b/modules/http/http_request.c +@@ -708,7 +708,7 @@ AP_DECLARE(void) ap_internal_fast_redirect(request_rec *rr, request_rec *r) + r->args = rr->args; + r->finfo = rr->finfo; + r->handler = rr->handler; +- ap_set_content_type(r, rr->content_type); ++ ap_set_content_type_ex(r, rr->content_type, AP_REQUEST_IS_TRUSTED_CT(rr)); + r->content_encoding = rr->content_encoding; + r->content_languages = rr->content_languages; + r->per_dir_config = rr->per_dir_config; +diff --git a/modules/ldap/util_ldap.c b/modules/ldap/util_ldap.c +index 39b1890..5e35852 100644 +--- a/modules/ldap/util_ldap.c ++++ b/modules/ldap/util_ldap.c +@@ -139,7 +139,7 @@ static int util_ldap_handler(request_rec *r) + st = (util_ldap_state_t *) ap_get_module_config(r->server->module_config, + &ldap_module); + +- ap_set_content_type(r, "text/html; charset=ISO-8859-1"); ++ ap_set_content_type_ex(r, "text/html; charset=ISO-8859-1", 1); + + if (r->header_only) + return OK; +diff --git a/modules/mappers/mod_imagemap.c b/modules/mappers/mod_imagemap.c +index 206c0b6..b2dca7e 100644 +--- a/modules/mappers/mod_imagemap.c ++++ b/modules/mappers/mod_imagemap.c +@@ -475,7 +475,7 @@ static int imap_reply(request_rec *r, const char *redirect) + + static void menu_header(request_rec *r, char *menu) + { +- ap_set_content_type(r, "text/html; charset=ISO-8859-1"); ++ ap_set_content_type_ex(r, "text/html; charset=ISO-8859-1", 1); + + ap_rvputs(r, DOCTYPE_HTML_3_2, "\nMenu for ", + ap_escape_html(r->pool, r->uri), +diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c +index b8b452d..3c0f5a8 100644 +--- a/modules/proxy/mod_proxy_balancer.c ++++ b/modules/proxy/mod_proxy_balancer.c +@@ -1471,7 +1471,7 @@ static void balancer_display_page(request_rec *r, proxy_server_conf *conf, + + if (usexml) { + char date[APR_RFC822_DATE_LEN]; +- ap_set_content_type(r, "text/xml"); ++ ap_set_content_type_ex(r, "text/xml", 1); + ap_rputs("<?xml version='1.0' encoding='UTF-8' ?>\n", r); + ap_rputs("<httpd:manager xmlns:httpd='http://httpd.apache.org'>\n", r); + ap_rputs(" <httpd:balancers>\n", r); diff --git a/SOURCES/httpd-2.4.57-r1919325.patch b/SOURCES/httpd-2.4.57-r1919325.patch new file mode 100644 index 0000000..bb88eff --- /dev/null +++ b/SOURCES/httpd-2.4.57-r1919325.patch @@ -0,0 +1,242 @@ +From a0a68b99d131741c1867cff321424892838fc4b3 Mon Sep 17 00:00:00 2001 +From: Yann Ylavic <ylavic@apache.org> +Date: Sat, 27 Jul 2024 13:35:53 +0000 +Subject: [PATCH] mod_rewrite: Better question mark tracking to avoid + UnsafeAllow3F. PR 69197. + +Track in do_expand() whether a '?' in the uri-path comes from a literal in +the substitution string or from an expansion (variable, lookup, ...). +In the former case it's safe to assume that it's the query-string separator +but for the other case it's not (could be a decoded %3f from r->uri). + +This allows to avoid [UnsafeAllow3F] for most cases. + + +Merges r1919325 from trunk +Reviewed by: ylavic, covener, jorton +Github: closes #462 + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1919545 13f79535-47bb-0310-9956-ffa450edef68 +--- + modules/mappers/mod_rewrite.c | 107 ++++++++++++++++++++++++++++------ + 1 file changed, 89 insertions(+), 18 deletions(-) + + +diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c +index 226602f..a56a85c 100644 +--- a/modules/mappers/mod_rewrite.c ++++ b/modules/mappers/mod_rewrite.c +@@ -2365,9 +2365,16 @@ static APR_INLINE char *find_char_in_curlies(char *s, int c) + * of an earlier expansion to include expansion specifiers that + * are interpreted by a later expansion, producing results that + * were not intended by the administrator. ++ * ++ * unsafe_qmark if not NULL will be set to 1 or 0 if a question mark ++ * is found respectively in a literal or in a lookup/expansion (whether ++ * it's the first or last qmark depends on [QSL]). Should be initialized ++ * to -1 and remains so if no qmark is found. + */ +-static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) ++static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry, ++ int *unsafe_qmark) + { ++#define EXPAND_SPECIALS "\\$%" + result_list *result, *current; + result_list sresult[SMALL_EXPANSION]; + unsigned spc = 0; +@@ -2375,8 +2382,29 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + char *p, *c; + apr_pool_t *pool = ctx->r->pool; + +- span = strcspn(input, "\\$%"); + inputlen = strlen(input); ++ if (!unsafe_qmark) { ++ span = strcspn(input, EXPAND_SPECIALS); ++ } ++ else { ++ span = strcspn(input, EXPAND_SPECIALS "?"); ++ if (input[span] == '?') { ++ /* this qmark is not from an expansion thus safe */ ++ *unsafe_qmark = 0; ++ ++ /* keep tracking only if interested in the last qmark */ ++ if (entry && (entry->flags & RULEFLAG_QSLAST)) { ++ do { ++ span++; ++ span += strcspn(input + span, EXPAND_SPECIALS "?"); ++ } while (input[span] == '?'); ++ } ++ else { ++ unsafe_qmark = NULL; ++ span += strcspn(input + span, EXPAND_SPECIALS); ++ } ++ } ++ } + + /* fast exit */ + if (inputlen == span) { +@@ -2394,6 +2422,8 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + + /* loop for specials */ + do { ++ int expanded = 0; ++ + /* prepare next entry */ + if (current->len) { + current->next = (spc < SMALL_EXPANSION) +@@ -2439,6 +2469,8 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + current->len = span; + current->string = p; + outlen += span; ++ ++ expanded = 1; + p = endp + 1; + } + +@@ -2478,19 +2510,18 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + } + + /* reuse of key variable as result */ +- key = lookup_map(ctx->r, map, do_expand(key, ctx, entry)); +- ++ key = lookup_map(ctx->r, map, do_expand(key, ctx, entry, NULL)); + if (!key && dflt && *dflt) { +- key = do_expand(dflt, ctx, entry); ++ key = do_expand(dflt, ctx, entry, NULL); + } +- +- if (key) { ++ if (key && *key) { + span = strlen(key); + current->len = span; + current->string = key; + outlen += span; + } + ++ expanded = 1; + p = endp + 1; + } + } +@@ -2520,8 +2551,9 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + current->len = span; + current->string = bri->source + bri->regmatch[n].rm_so; + } +- + outlen += span; ++ ++ expanded = 1; + } + + p += 2; +@@ -2534,8 +2566,41 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + ++outlen; + } + ++ if (unsafe_qmark && expanded && current->len ++ && memchr(current->string, '?', current->len)) { ++ /* this qmark is from an expansion thus unsafe */ ++ *unsafe_qmark = 1; ++ ++ /* keep tracking only if interested in the last qmark */ ++ if (!entry || !(entry->flags & RULEFLAG_QSLAST)) { ++ unsafe_qmark = NULL; ++ } ++ } ++ + /* check the remainder */ +- if (*p && (span = strcspn(p, "\\$%")) > 0) { ++ if (!unsafe_qmark) { ++ span = strcspn(p, EXPAND_SPECIALS); ++ } ++ else { ++ span = strcspn(p, EXPAND_SPECIALS "?"); ++ if (p[span] == '?') { ++ /* this qmark is not from an expansion thus safe */ ++ *unsafe_qmark = 0; ++ ++ /* keep tracking only if interested in the last qmark */ ++ if (entry && (entry->flags & RULEFLAG_QSLAST)) { ++ do { ++ span++; ++ span += strcspn(p + span, EXPAND_SPECIALS "?"); ++ } while (p[span] == '?'); ++ } ++ else { ++ unsafe_qmark = NULL; ++ span += strcspn(p + span, EXPAND_SPECIALS); ++ } ++ } ++ } ++ if (span > 0) { + if (current->len) { + current->next = (spc < SMALL_EXPANSION) + ? &(sresult[spc++]) +@@ -2580,7 +2645,7 @@ static void do_expand_env(data_item *env, rewrite_ctx *ctx) + char *name, *val; + + while (env) { +- name = do_expand(env->data, ctx, NULL); ++ name = do_expand(env->data, ctx, NULL, NULL); + if (*name == '!') { + name++; + apr_table_unset(ctx->r->subprocess_env, name); +@@ -2714,7 +2779,7 @@ static void add_cookie(request_rec *r, char *s) + static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx) + { + while (cookie) { +- add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL)); ++ add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL, NULL)); + cookie = cookie->next; + } + +@@ -3992,7 +4057,7 @@ static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx) + int basis; + + if (p->ptype != CONDPAT_AP_EXPR) +- input = do_expand(p->input, ctx, NULL); ++ input = do_expand(p->input, ctx, NULL, NULL); + + switch (p->ptype) { + case CONDPAT_FILE_EXISTS: +@@ -4156,7 +4221,7 @@ static APR_INLINE void force_type_handler(rewriterule_entry *p, + char *expanded; + + if (p->forced_mimetype) { +- expanded = do_expand(p->forced_mimetype, ctx, p); ++ expanded = do_expand(p->forced_mimetype, ctx, p, NULL); + + if (*expanded) { + ap_str_tolower(expanded); +@@ -4170,7 +4235,7 @@ static APR_INLINE void force_type_handler(rewriterule_entry *p, + } + + if (p->forced_handler) { +- expanded = do_expand(p->forced_handler, ctx, p); ++ expanded = do_expand(p->forced_handler, ctx, p, NULL); + + if (*expanded) { + ap_str_tolower(expanded); +@@ -4307,12 +4372,18 @@ static rule_return_type apply_rewrite_rule(rewriterule_entry *p, + + /* expand the result */ + if (!(p->flags & RULEFLAG_NOSUB)) { +- newuri = do_expand(p->output, ctx, p); ++ int unsafe_qmark = -1; ++ ++ if (p->flags & RULEFLAG_UNSAFE_ALLOW3F) { ++ newuri = do_expand(p->output, ctx, p, NULL); ++ } ++ else { ++ newuri = do_expand(p->output, ctx, p, &unsafe_qmark); ++ } + rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri, + newuri)); +- if (!(p->flags & RULEFLAG_UNSAFE_ALLOW3F) && +- ap_strcasestr(r->unparsed_uri, "%3f") && +- ap_strchr_c(newuri, '?')) { ++ ++ if (unsafe_qmark > 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO() + "Unsafe URL with %%3f URL rewritten without " + "UnsafeAllow3F"); diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index aee9f76..512abe8 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -13,7 +13,7 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.57 -Release: 11%{?dist} +Release: 11%{?dist}.1 URL: https://httpd.apache.org/ Source0: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 Source1: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2.asc @@ -99,7 +99,6 @@ Patch53: httpd-2.4.57-r1912477+.patch # https://issues.redhat.com/browse/RHEL-6600 Patch54: httpd-2.4.57-r1912081.patch - # Bug fixes # https://bugzilla.redhat.com/show_bug.cgi?id=1397243 Patch60: httpd-2.4.43-enable-sslv3.patch @@ -122,6 +121,8 @@ Patch70: httpd-2.4.57-mod_status-duplicate-key.patch Patch71: httpd-2.4.57-davenoent.patch # https://issues.redhat.com/browse/RHEL-17686 Patch72: httpd-2.4.57-r1884505+.patch +# https://bz.apache.org/bugzilla/show_bug.cgi?id=69197 +Patch73: httpd-2.4.57-r1919325.patch # Security fixes # https://bugzilla.redhat.com/show_bug.cgi?id=... @@ -138,7 +139,12 @@ Patch202: httpd-2.4.57-CVE-2024-39573.patch Patch204: httpd-2.4.57-CVE-2024-38474+.patch # https://bugzilla.redhat.com/show_bug.cgi?id=2295012 Patch206: httpd-2.4.57-CVE-2024-38473.patch - +# https://bugzilla.redhat.com/show_bug.cgi?id=2295015 +Patch207: httpd-2.4.57-CVE-2024-38476.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2297362 +# https://bugzilla.redhat.com/show_bug.cgi?id=2295761 +# part of CVE-2024-38476 fix +Patch208: httpd-2.4.57-CVE-2024-39884+.patch License: ASL 2.0 BuildRequires: gcc, autoconf, pkgconfig, findutils, xmlto @@ -315,7 +321,11 @@ written in the Lua programming language. %patch201 -p1 -b .CVE-2024-38477 %patch202 -p1 -b .CVE-2024-39573 %patch204 -p1 -b .CVE-2024-38474+ +# CVE-2024-38474 regression fix +%patch73 -p1 -b .r1919325 %patch206 -p1 -b .CVE-2024-38473 +%patch207 -p1 -b .CVE-2024-38476 +%patch208 -p1 -b .CVE-2024-39884+ # Patch in the vendor string sed -i '/^#define PLATFORM/s/Unix/%{vstring}/' os/unix/os.h @@ -876,6 +886,11 @@ exit $rv %{_rpmconfigdir}/macros.d/macros.httpd %changelog +* Mon Aug 05 2024 Luboš Uhliarik <luhliari@redhat.com> - 2.4.57-11.1 +- Resolves: RHEL-46047 - httpd: Security issues via backend applications whose + response headers are malicious or exploitable (CVE-2024-38476) +- Resolves: RHEL-53021 - Regression introduced by CVE-2024-38474 fix + * Thu Jul 04 2024 Luboš Uhliarik <luhliari@redhat.com> - 2.4.57-11 - Resolves: RHEL-45792 - httpd: Encoding problem in mod_proxy (CVE-2024-38473)