From ef98f4f494ff2f99d736a3716cd31219688b46f5 Mon Sep 17 00:00:00 2001
From: Eric Covener <covener@apache.org>
Date: Mon, 7 Jul 2025 12:12:49 +0000
Subject: [PATCH] backport 1927038 from trunk

  improve h2 header error handling

Rewviewed By: icing, covener, rpluem


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1927046 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
index 840c491..18492fe 100644
--- a/modules/http2/h2_request.c
+++ b/modules/http2/h2_request.c
@@ -38,22 +38,39 @@
 #include "h2_util.h"
 
 
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+                              const char *scheme, const char *authority,
+                              const char *path, apr_table_t *header)
+{
+    h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
+
+    req->method         = method;
+    req->scheme         = scheme;
+    req->authority      = authority;
+    req->path           = path;
+    req->headers        = header? header : apr_table_make(pool, 10);
+    req->request_time   = apr_time_now();
+
+    return req;
+}
+
 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));
+                                  value, x->scratch, strlen(value));
     return (x->status == APR_SUCCESS)? 1 : 0;
 }
 
 apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
-                                request_rec *r)
+                                request_rec *r, h2_hd_scratch *scratch)
 {
     h2_request *req;
     const char *scheme, *authority, *path;
@@ -92,6 +109,7 @@ 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;
@@ -100,7 +118,8 @@ apr_status_t h2_request_rcreate(h2_request **preq, 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, struct h2_hd_scratch *scratch,
+				   size_t vlen)
 {
     apr_status_t status = APR_SUCCESS;
     
@@ -145,7 +164,7 @@ 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, scratch, vlen);
     }
     
     return status;
diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h
index 48aee09..45d4a83 100644
--- a/modules/http2/h2_request.h
+++ b/modules/http2/h2_request.h
@@ -19,12 +19,21 @@
 
 #include "h2.h"
 
+struct h2_hd_scratch;
+
+h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
+                              const char *scheme, const char *authority,
+                              const char *path, apr_table_t *header);
+
 apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
-                                request_rec *r);
+                                request_rec *r,
+                                struct h2_hd_scratch *scratch);
 
 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,
+                                   struct h2_hd_scratch *scratch,
+				   size_t vlen);
 
 apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
                                     const char *name, size_t nlen,
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
index 0ed9150..8241a14 100644
--- a/modules/http2/h2_session.c
+++ b/modules/http2/h2_session.c
@@ -109,13 +109,29 @@ static void cleanup_unprocessed_streams(h2_session *session)
     h2_mplx_m_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_status_t rv;
     
-    apr_pool_create(&stream_pool, session->pool);
+    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, 
@@ -906,6 +922,14 @@ 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);
+
     session->padding_max = h2_config_sgeti(s, H2_CONF_PADDING_BITS);
     if (session->padding_max) {
         session->padding_max = (0x01 << session->padding_max) - 1; 
@@ -949,7 +973,7 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
     
     n = h2_config_sgeti(s, H2_CONF_PUSH_DIARY_SIZE);
     session->push_diary = h2_push_diary_create(session->pool, n);
-    
+
     if (APLOGcdebug(c)) {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, 
                       H2_SSSN_LOG(APLOGNO(03200), session, 
@@ -1989,7 +2013,9 @@ static void on_stream_state_enter(void *ctx, h2_stream *stream)
             break;
         case H2_SS_CLEANUP:
             nghttp2_session_set_stream_user_data(session->ngh2, stream->id, NULL);
+            update_child_status(session, SERVER_BUSY_WRITE, "done");
             h2_mplx_m_stream_cleanup(session->mplx, stream);
+            stream = NULL;
             break;
         default:
             break;
diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h
index 4f74b56..30cd52b 100644
--- a/modules/http2/h2_session.h
+++ b/modules/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 a secondary */
 
+    h2_hd_scratch hd_scratch;
+
 } h2_session;
 
 const char *h2_session_state_str(h2_session_state state);
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
index 5bef83f..0f4722e 100644
--- a/modules/http2/h2_stream.c
+++ b/modules/http2/h2_stream.c
@@ -615,7 +615,8 @@ 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, r,
+		    &stream->session->hd_scratch);
     if (status == APR_SUCCESS) {
         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, 
                       H2_STRM_LOG(APLOGNO(03058), stream, 
@@ -645,10 +646,11 @@ static void set_error_response(h2_stream *stream, int http_status)
 
 static apr_status_t add_trailer(h2_stream *stream,
                                 const char *name, size_t nlen,
-                                const char *value, size_t vlen)
+                                const char *value, size_t vlen,
+                                h2_hd_scratch *scratch)
 {
     conn_rec *c = stream->session->c;
-    char *hname, *hvalue;
+    const char *existing;
 
     if (nlen == 0 || name[0] == ':') {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c, 
@@ -662,12 +664,35 @@ 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;
 }
@@ -678,66 +703,95 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
 {
     h2_session *session = stream->session;
     int error = 0;
-    apr_status_t status;
+    apr_status_t status = APR_SUCCESS;
     
+    H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
     if (stream->has_response) {
         return APR_EINVAL;    
     }
-    ++stream->request_headers_added;
+
     if (name[0] == ':') {
-        if ((vlen) > session->s->limit_req_line) {
+        if (vlen > APR_INT32_MAX || (int)vlen > session->s->limit_req_line) {
             /* pseudo header: approximation of request line size check */
-            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,  
-                          H2_STRM_LOG(APLOGNO(10178), stream, 
-                                      "Request pseudo header exceeds "
-                                      "LimitRequestFieldSize: %s"), name);
+            if (!h2_stream_is_ready(stream)) {
+                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+                              H2_STRM_LOG(APLOGNO(10178), stream,
+                                          "Request pseudo header exceeds "
+                                          "LimitRequestFieldSize: %s"), name);
+            }
             error = HTTP_REQUEST_URI_TOO_LARGE;
+            goto cleanup;
         }
     }
-    else if ((nlen + 2 + vlen) > session->s->limit_req_fieldsize) {
-        /* header too long */
-        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,  
-                      H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
-                                  "LimitRequestFieldSize: %.*s"),
-                      (int)H2MIN(nlen, 80), name);
-        error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
-    }
     
-    if (stream->request_headers_added > session->s->limit_req_fields + 4) {
-        /* too many header lines, include 4 pseudo headers */
-        if (stream->request_headers_added 
-            > session->s->limit_req_fields + 4 + 100) {
-            /* yeah, right */
-            h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
-            return APR_ECONNRESET;
-        }
-        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c, 
-                      H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
-                                  "exceeds LimitRequestFields"));
-        error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
-    }
-    
-    if (error) {
-        ++stream->request_headers_failed;
-        set_error_response(stream, error);
-        return APR_EINVAL; 
+    if (session->s->limit_req_fields > 0 
+        && stream->request_headers_added > session->s->limit_req_fields) {
+        /* already over limit, count this attempt, but do not take it in */
+        ++stream->request_headers_added;
     }
     else if (H2_SS_IDLE == stream->state) {
+        int was_added;
         if (!stream->rtmp) {
-            stream->rtmp = h2_req_create(stream->id, stream->pool, 
-                                         NULL, NULL, NULL, NULL, NULL, 0);
+            stream->rtmp = h2_request_create(stream->id, stream->pool,
+                                             NULL, NULL, NULL, NULL, NULL);
         }
         status = h2_request_add_header(stream->rtmp, stream->pool,
-                                       name, nlen, value, vlen);
+                                       name, nlen, value,
+                                       &session->hd_scratch,
+				       vlen);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
+                      H2_STRM_MSG(stream, "add_header: '%.*s: %.*s"),
+                      (int)nlen, name, (int)vlen, value);
+        if (was_added) ++stream->request_headers_added;
     }
     else if (H2_SS_OPEN == stream->state) {
-        status = add_trailer(stream, name, nlen, value, vlen);
+        status = add_trailer(stream, name, nlen, value, vlen,
+                             &session->hd_scratch);
+        if (!status) ++stream->request_headers_added;
     }
     else {
         status = APR_EINVAL;
+        goto cleanup;
     }
     
-    if (status != APR_SUCCESS) {
+    if (APR_EINVAL == status) {
+        /* header too long */
+        if (!h2_stream_is_ready(stream) && !stream->request_headers_failed) {
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+                          H2_STRM_LOG(APLOGNO(10180), stream,
+                          "Request header exceeds LimitRequestFieldSize(%d): %.*s"),
+                          (int)session->hd_scratch.max_len,
+                          (int)H2MIN(nlen, 80), name);
+        }
+        error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
+        goto cleanup;
+    }
+
+    if (session->s->limit_req_fields > 0 
+        && stream->request_headers_added > session->s->limit_req_fields) {
+        /* too many header lines */
+        if (stream->request_headers_added > session->s->limit_req_fields + 100) {
+            /* yeah, right, this request is way over the limit, say goodbye */
+            h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
+            return APR_ECONNRESET;
+        }
+        if (!h2_stream_is_ready(stream)) {
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,
+                          H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
+                                      "exceeds LimitRequestFields(%d)"),
+                                      (int)session->s->limit_req_fields);
+        }
+        error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
+        goto cleanup;
+    }
+
+cleanup:
+    if (error) {
+        ++stream->request_headers_failed;
+        set_error_response(stream, error);
+        return APR_EINVAL; 
+    }
+    else if (status != APR_SUCCESS) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                       H2_STRM_MSG(stream, "header %s not accepted"), name);
         h2_stream_dispatch(stream, H2_SEV_CANCELLED);
diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
index 987e21f..d1e5f62 100644
--- a/modules/http2/h2_stream.h
+++ b/modules/http2/h2_stream.h
@@ -62,6 +62,18 @@ typedef struct h2_stream_monitor {
                                              trigger a state change */
 } h2_stream_monitor;
 
+#ifdef AP_DEBUG
+#define H2_STRM_MAGIC_OK     0x5354524d
+#define H2_STRM_MAGIC_SDEL   0x5344454c
+#define H2_STRM_MAGIC_PDEL   0x5044454c
+
+#define H2_STRM_ASSIGN_MAGIC(s,m)  ((s)->magic = m)
+#define H2_STRM_ASSERT_MAGIC(s,m)  ap_assert((s)->magic == m)
+#else
+#define H2_STRM_ASSIGN_MAGIC(s,m)  ((void)0)
+#define H2_STRM_ASSERT_MAGIC(s,m)  ((void)0)
+#endif
+
 struct h2_stream {
     int id;                     /* http2 stream identifier */
     int initiated_on;           /* initiating stream id (PUSH) or 0 */
diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c
index 06110f5..db92ce1 100644
--- a/modules/http2/h2_util.c
+++ b/modules/http2/h2_util.c
@@ -1804,38 +1804,64 @@ int h2_res_ignore_trailer(const char *name, size_t len)
 
 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, h2_hd_scratch *scratch, size_t vlen)
 {
-    char *hname, *hvalue;
-    
+    const char *existing;
+
     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");
+    else if (nlen == sizeof("cookie")-1
+             && !ap_cstr_casecmp("cookie", (const char *)name)) {
+        existing = apr_table_get(headers, "cookie");
         if (existing) {
-            char *nval;
-            
             /* Cookie header 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) + vlen + nlen + 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;
         }
     }
-    else if (H2_HD_MATCH_LIT("host", name, nlen)) {
+    else if (nlen == sizeof("host")-1
+             && !ap_cstr_casecmp("host", (const char *)name)) {
         if (apr_table_get(headers, "Host")) {
             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 {
+        h2_util_camel_case_header(scratch->name, nlen);
+        apr_table_set(headers, scratch->name, scratch->value);
+    }
     return APR_SUCCESS;
 }
 
diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h
index 0e2ae57..f79d9cb 100644
--- a/modules/http2/h2_util.h
+++ b/modules/http2/h2_util.h
@@ -410,9 +410,17 @@ apr_status_t h2_res_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);
 
+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, h2_hd_scratch *scratch,
+			       size_t vlen);
 
 /*******************************************************************************
  * h2_request helpers
