Resolves: RHEL-166277 - httpd:2.4/httpd: Apache HTTP Server: HTTP/2 DoS by

Memory Increase (CVE-2025-53020)
This commit is contained in:
Luboš Uhliarik 2026-05-19 22:06:37 +02:00
parent 35d13677a9
commit 01f4cd5621
2 changed files with 400 additions and 1 deletions

View File

@ -0,0 +1,392 @@
From 0e9e22e6874dfc37683a93828ab6b8b0ce2e84d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lubo=C5=A1=20Uhliarik?= <luhliari@redhat.com>
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

View File

@ -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 <luhliari@redhat.com> - 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 <luhliari@redhat.com> - 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)