diff --git a/mod_http2-1.15.7-CVE-2025-53020.patch b/mod_http2-1.15.7-CVE-2025-53020.patch new file mode 100644 index 0000000..b5b19bb --- /dev/null +++ b/mod_http2-1.15.7-CVE-2025-53020.patch @@ -0,0 +1,392 @@ +From 0e9e22e6874dfc37683a93828ab6b8b0ce2e84d6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Lubo=C5=A1=20Uhliarik?= +Date: Mon, 18 May 2026 20:04:48 +0200 +Subject: [PATCH] backport CVE-2025-53020 to 1.15.7 + +backport 1927038 from trunk + +improve h2 header error handling +--- + mod_http2/h2_request.c | 24 ++++++++++------ + mod_http2/h2_request.h | 9 ++++-- + mod_http2/h2_session.c | 33 ++++++++++++++++++++-- + mod_http2/h2_session.h | 3 ++ + mod_http2/h2_stream.c | 52 ++++++++++++++++++++++++++--------- + mod_http2/h2_util.c | 62 ++++++++++++++++++++++++++++++------------ + mod_http2/h2_util.h | 13 +++++++-- + 7 files changed, 151 insertions(+), 45 deletions(-) + +diff --git a/mod_http2/h2_request.c b/mod_http2/h2_request.c +index 1892967..e7d03b0 100644 +--- a/mod_http2/h2_request.c ++++ b/mod_http2/h2_request.c +@@ -42,17 +42,21 @@ typedef struct { + apr_table_t *headers; + apr_pool_t *pool; + apr_status_t status; ++ h2_hd_scratch *scratch; + } h1_ctx; + + static int set_h1_header(void *ctx, const char *key, const char *value) + { + h1_ctx *x = ctx; +- x->status = h2_req_add_header(x->headers, x->pool, key, strlen(key), +- value, strlen(value)); ++ int was_added; ++ x->status = h2_req_add_header(x->headers, x->pool, key, strlen(key), ++ value, strlen(value), ++ x->scratch, &was_added); + return (x->status == APR_SUCCESS)? 1 : 0; + } + +-apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, ++apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, ++ h2_hd_scratch *scratch, + request_rec *r) + { + h2_request *req; +@@ -92,15 +96,18 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, + x.pool = pool; + x.headers = req->headers; + x.status = APR_SUCCESS; ++ x.scratch = scratch; + apr_table_do(set_h1_header, &x, r->headers_in, NULL); +- ++ + *preq = req; + return x.status; + } + +-apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, ++apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, + const char *name, size_t nlen, +- const char *value, size_t vlen) ++ const char *value, size_t vlen, ++ h2_hd_scratch *scratch, ++ int *pwas_added) + { + apr_status_t status = APR_SUCCESS; + +@@ -145,9 +152,10 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, + } + else { + /* non-pseudo header, append to work bucket of stream */ +- status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen); ++ status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen, ++ scratch, pwas_added); + } +- ++ + return status; + } + +diff --git a/mod_http2/h2_request.h b/mod_http2/h2_request.h +index 48aee09..d747787 100644 +--- a/mod_http2/h2_request.h ++++ b/mod_http2/h2_request.h +@@ -19,12 +19,17 @@ + + #include "h2.h" + +-apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, ++struct h2_hd_scratch; ++ ++apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, ++ struct h2_hd_scratch *scratch, + request_rec *r); + + apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, + const char *name, size_t nlen, +- const char *value, size_t vlen); ++ const char *value, size_t vlen, ++ struct h2_hd_scratch *scratch, ++ int *pwas_added); + + apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool, + const char *name, size_t nlen, +diff --git a/mod_http2/h2_session.c b/mod_http2/h2_session.c +index 2a97ad9..4af3455 100644 +--- a/mod_http2/h2_session.c ++++ b/mod_http2/h2_session.c +@@ -109,13 +109,29 @@ static void cleanup_unprocessed_streams(h2_session *session) + h2_mplx_stream_do(session->mplx, rst_unprocessed_stream, session); + } + ++/* APR callback invoked if allocation fails. */ ++static int abort_on_oom(int retcode) ++{ ++ ap_abort_on_oom(); ++ return retcode; /* unreachable, hopefully. */ ++} ++ + static h2_stream *h2_session_open_stream(h2_session *session, int stream_id, + int initiated_on) + { + h2_stream * stream; ++ apr_allocator_t *allocator; + apr_pool_t *stream_pool; +- +- apr_pool_create(&stream_pool, session->pool); ++ apr_status_t rv; ++ ++ rv = apr_allocator_create(&allocator); ++ if (rv != APR_SUCCESS) ++ return NULL; ++ ++ apr_allocator_max_free_set(allocator, ap_max_mem_free); ++ apr_pool_create_ex(&stream_pool, session->pool, NULL, allocator); ++ apr_allocator_owner_set(allocator, stream_pool); ++ apr_pool_abort_set(abort_on_oom, stream_pool); + apr_pool_tag(stream_pool, "h2_stream"); + + stream = h2_stream_create(stream_id, stream_pool, session, +@@ -921,6 +937,19 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec * + ap_add_input_filter("H2_IN", session->cin, r, c); + + h2_conn_io_init(&session->io, c, s); ++ ++ /* setup request header scratch buffers */ ++ session->hd_scratch.max_len = session->s->limit_req_fieldsize? ++ session->s->limit_req_fieldsize : 8190; ++ session->hd_scratch.name = ++ apr_pcalloc(session->pool, session->hd_scratch.max_len + 1); ++ session->hd_scratch.value = ++ apr_pcalloc(session->pool, session->hd_scratch.max_len + 1); ++ if (!session->hd_scratch.name || !session->hd_scratch.value) { ++ apr_pool_destroy(pool); ++ return APR_ENOMEM; ++ } ++ + session->padding_max = h2_config_sgeti(s, H2_CONF_PADDING_BITS); + if (session->padding_max) { + session->padding_max = (0x01 << session->padding_max) - 1; +diff --git a/mod_http2/h2_session.h b/mod_http2/h2_session.h +index cd08fc2..9d3a652 100644 +--- a/mod_http2/h2_session.h ++++ b/mod_http2/h2_session.h +@@ -39,6 +39,7 @@ + */ + + #include "h2.h" ++#include "h2_util.h" + + struct apr_thread_mutext_t; + struct apr_thread_cond_t; +@@ -134,6 +135,8 @@ typedef struct h2_session { + struct h2_iqueue *in_pending; /* all streams with input pending */ + struct h2_iqueue *in_process; /* all streams ready for processing on slave */ + ++ h2_hd_scratch hd_scratch; ++ + } h2_session; + + const char *h2_session_state_str(h2_session_state state); +diff --git a/mod_http2/h2_stream.c b/mod_http2/h2_stream.c +index adbd5d2..0f20e22 100644 +--- a/mod_http2/h2_stream.c ++++ b/mod_http2/h2_stream.c +@@ -615,7 +615,7 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream, + if (stream->rst_error) { + return APR_ECONNRESET; + } +- status = h2_request_rcreate(&req, stream->pool, r); ++ status = h2_request_rcreate(&req, stream->pool, &stream->session->hd_scratch, r); + if (status == APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, + H2_STRM_LOG(APLOGNO(03058), stream, +@@ -648,11 +648,12 @@ static apr_status_t add_trailer(h2_stream *stream, + const char *value, size_t vlen) + { + conn_rec *c = stream->session->c; +- char *hname, *hvalue; ++ h2_hd_scratch *scratch = &stream->session->hd_scratch; ++ const char *existing; + + if (nlen == 0 || name[0] == ':') { +- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c, +- H2_STRM_LOG(APLOGNO(03060), stream, ++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c, ++ H2_STRM_LOG(APLOGNO(03060), stream, + "pseudo header in trailer")); + return APR_EINVAL; + } +@@ -662,13 +663,36 @@ static apr_status_t add_trailer(h2_stream *stream, + if (!stream->trailers) { + stream->trailers = apr_table_make(stream->pool, 5); + } +- hname = apr_pstrndup(stream->pool, name, nlen); +- hvalue = apr_pstrndup(stream->pool, value, vlen); +- h2_util_camel_case_header(hname, nlen); +- apr_table_mergen(stream->trailers, hname, hvalue); +- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, +- H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue); +- ++ ++ if (((nlen + vlen + 2) > scratch->max_len)) ++ return APR_EINVAL; ++ ++ /* We need 0-terminated strings to operate on apr_table */ ++ AP_DEBUG_ASSERT(nlen < scratch->max_len); ++ memcpy(scratch->name, name, nlen); ++ scratch->name[nlen] = 0; ++ AP_DEBUG_ASSERT(vlen < scratch->max_len); ++ memcpy(scratch->value, value, vlen); ++ scratch->value[vlen] = 0; ++ ++ existing = apr_table_get(stream->trailers, scratch->name); ++ if (existing) { ++ if (!vlen) /* not adding a 0-length value to existing */ ++ return APR_SUCCESS; ++ if ((strlen(existing) + 2 + vlen + nlen + 2 > scratch->max_len)) { ++ /* "name: existing, value" is too long */ ++ return APR_EINVAL; ++ } ++ apr_table_merge(stream->trailers, scratch->name, scratch->value); ++ } ++ else { ++ h2_util_camel_case_header(scratch->name, nlen); ++ apr_table_set(stream->trailers, scratch->name, scratch->value); ++ } ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, ++ H2_STRM_MSG(stream, "added trailer '%s: %s'"), ++ scratch->name, scratch->value); ++ + return APR_SUCCESS; + } + +@@ -729,12 +753,14 @@ apr_status_t h2_stream_add_header(h2_stream *stream, + return APR_EINVAL; + } + else if (H2_SS_IDLE == stream->state) { ++ int was_added = 0; + if (!stream->rtmp) { +- stream->rtmp = h2_req_create(stream->id, stream->pool, ++ stream->rtmp = h2_req_create(stream->id, stream->pool, + NULL, NULL, NULL, NULL, NULL, 0); + } + status = h2_request_add_header(stream->rtmp, stream->pool, +- name, nlen, value, vlen); ++ name, nlen, value, vlen, ++ &session->hd_scratch, &was_added); + } + else if (H2_SS_OPEN == stream->state) { + status = add_trailer(stream, name, nlen, value, vlen); +diff --git a/mod_http2/h2_util.c b/mod_http2/h2_util.c +index 2943b76..c20e879 100644 +--- a/mod_http2/h2_util.c ++++ b/mod_http2/h2_util.c +@@ -1802,26 +1802,31 @@ int h2_res_ignore_trailer(const char *name, size_t len) + return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len); + } + +-apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, ++apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, + const char *name, size_t nlen, +- const char *value, size_t vlen) ++ const char *value, size_t vlen, ++ h2_hd_scratch *scratch, int *pwas_added) + { +- char *hname, *hvalue; +- ++ const char *existing; ++ ++ *pwas_added = 0; + if (h2_req_ignore_header(name, nlen)) { + return APR_SUCCESS; + } + else if (H2_HD_MATCH_LIT("cookie", name, nlen)) { +- const char *existing = apr_table_get(headers, "cookie"); ++ existing = apr_table_get(headers, "Cookie"); + if (existing) { +- char *nval; +- +- /* Cookie header come separately in HTTP/2, but need ++ /* Cookie headers come separately in HTTP/2, but need + * to be merged by "; " (instead of default ", ") + */ +- hvalue = apr_pstrndup(pool, value, vlen); +- nval = apr_psprintf(pool, "%s; %s", existing, hvalue); +- apr_table_setn(headers, "Cookie", nval); ++ if ((strlen(existing) + nlen + vlen + 4) ++ > scratch->max_len) { ++ /* "key: oldval, nval" is too long */ ++ return APR_EINVAL; ++ } ++ apr_table_setn(headers, "Cookie", ++ apr_psprintf(pool, "%s; %.*s", existing, ++ (int)vlen, value)); + return APR_SUCCESS; + } + } +@@ -1830,12 +1835,35 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, + return APR_SUCCESS; /* ignore duplicate */ + } + } +- +- hname = apr_pstrndup(pool, name, nlen); +- hvalue = apr_pstrndup(pool, value, vlen); +- h2_util_camel_case_header(hname, nlen); +- apr_table_mergen(headers, hname, hvalue); +- ++ ++ if (((nlen + vlen + 2) > scratch->max_len)) ++ return APR_EINVAL; ++ ++ /* We need 0-terminated strings to operate on apr_table */ ++ AP_DEBUG_ASSERT(nlen < scratch->max_len); ++ memcpy(scratch->name, name, nlen); ++ scratch->name[nlen] = 0; ++ AP_DEBUG_ASSERT(vlen < scratch->max_len); ++ memcpy(scratch->value, value, vlen); ++ scratch->value[vlen] = 0; ++ ++ existing = apr_table_get(headers, scratch->name); ++ if (existing) { ++ if (!vlen) /* not adding a 0-length value to existing */ ++ return APR_SUCCESS; ++ if ((strlen(existing) + 2 + vlen + nlen + 2) ++ > scratch->max_len) { ++ /* "name: existing, value" is too long */ ++ return APR_EINVAL; ++ } ++ apr_table_merge(headers, scratch->name, scratch->value); ++ } ++ else { ++ *pwas_added = 1; ++ h2_util_camel_case_header(scratch->name, nlen); ++ apr_table_set(headers, scratch->name, scratch->value); ++ } ++ + return APR_SUCCESS; + } + +diff --git a/mod_http2/h2_util.h b/mod_http2/h2_util.h +index 6c676eb..59c931e 100644 +--- a/mod_http2/h2_util.h ++++ b/mod_http2/h2_util.h +@@ -407,12 +407,19 @@ apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, + struct h2_headers *headers); + apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + struct h2_headers *headers); +-apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, ++apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + const struct h2_request *req); + +-apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, ++typedef struct h2_hd_scratch { ++ size_t max_len; /* header field size name + ': ' + value */ ++ char *name; /* max_len+1 sized */ ++ char *value; /* max_len+1 sized */ ++} h2_hd_scratch; ++ ++apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, + const char *name, size_t nlen, +- const char *value, size_t vlen); ++ const char *value, size_t vlen, ++ h2_hd_scratch *scratch, int *pwas_added); + + /******************************************************************************* + * h2_request helpers +-- +2.44.0 + diff --git a/mod_http2.spec b/mod_http2.spec index 2670c8c..f0f5ab5 100644 --- a/mod_http2.spec +++ b/mod_http2.spec @@ -3,7 +3,7 @@ Name: mod_http2 Version: 1.15.7 -Release: 10%{?dist}.4 +Release: 10%{?dist}.5 Summary: module implementing HTTP/2 for Apache 2 Group: System Environment/Daemons License: ASL 2.0 @@ -30,6 +30,8 @@ Patch11: mod_http2-1.15.7-r1918628.patch Patch12: mod_http2-1.15.7-fix-mood-change.patch # https://bugzilla.redhat.com/show_bug.cgi?id=2374578 Patch13: mod_http2-1.15.7-CVE-2025-49630.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2379343 +Patch14: mod_http2-1.15.7-CVE-2025-53020.patch BuildRequires: pkgconfig, httpd-devel >= 2.4.20, libnghttp2-devel >= 1.7.0, openssl-devel >= 1.0.2 Requires: httpd-mmn = %{_httpd_mmn} @@ -54,6 +56,7 @@ top of libnghttp2 for httpd 2.4 servers. %patch11 -p1 -b .r1918628 %patch12 -p1 -b .fix-mood-change %patch13 -p1 -b .CVE-2025-49630 +%patch14 -p1 -b .CVE-2025-53020 %build %configure @@ -80,6 +83,10 @@ make check %{_httpd_moddir}/mod_proxy_http2.so %changelog +* Tue May 05 2026 Luboš Uhliarik - 1.15.7-10.5 +- Resolves: RHEL-166277 - httpd:2.4/httpd: Apache HTTP Server: HTTP/2 DoS by + Memory Increase (CVE-2025-53020) + * Mon Jul 28 2025 Luboš Uhliarik - 1.15.7-10.4 - Resolves: RHEL-105186 - httpd:2.4/httpd: untrusted input from a client causes an assertion to fail in the Apache mod_proxy_http2 module (CVE-2025-49630)