]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
backport 1927038 from trunk
authorEric Covener <covener@apache.org>
Mon, 7 Jul 2025 12:12:49 +0000 (12:12 +0000)
committerEric Covener <covener@apache.org>
Mon, 7 Jul 2025 12:12:49 +0000 (12:12 +0000)
  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

modules/http2/h2_request.c
modules/http2/h2_request.h
modules/http2/h2_session.c
modules/http2/h2_session.h
modules/http2/h2_stream.c
modules/http2/h2_util.c
modules/http2/h2_util.h
test/modules/http2/test_200_header_invalid.py

index 2713947c37769aa979a1d2cfa2b10eb3b88b13ca..6373e0a244ddbaf1bd8b014795d2c3f0110f76c4 100644 (file)
@@ -64,18 +64,20 @@ 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;
     int was_added;
-    h2_req_add_header(x->headers, x->pool, key, strlen(key), value, strlen(value), 0, &was_added);
+    h2_req_add_header(x->headers, x->pool, key, strlen(key),
+                      value, strlen(value), x->scratch, &was_added);
     return 1;
 }
 
 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;
@@ -125,6 +127,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;
@@ -134,7 +137,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,
-                                   size_t max_field_len, int *pwas_added)
+                                   struct h2_hd_scratch *scratch,
+                                   int *pwas_added)
 {
     apr_status_t status = APR_SUCCESS;
 
@@ -185,7 +189,7 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
     else {
         /* non-pseudo header, add to table */
         status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen,
-                                   max_field_len, pwas_added);
+                                   scratch, pwas_added);
     }
 
     return status;
index 7e20b697246da2f89da3d8cc7bc9256b8a364e25..ae6b6a2510c0269cc680afa154c7f921c29501c3 100644 (file)
 
 #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,
-                                   size_t max_field_len, int *pwas_added);
+                                   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,
index fc8b6119ae8dbdd3ce81cc5f7a9eb5d06e4b240a..a5f1872bc203946323ba8c579ed69bdeeeec318c 100644 (file)
@@ -109,13 +109,29 @@ static void cleanup_unprocessed_streams(h2_session *session)
     h2_mplx_c1_streams_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, 
@@ -972,6 +988,14 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
     }
 
     h2_c1_io_init(&session->io, session);
+    /* 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; 
@@ -1032,7 +1056,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, 
@@ -1699,9 +1723,10 @@ 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", stream);
             h2_mplx_c1_stream_cleanup(session->mplx, stream, &session->open_streams);
+            stream = NULL;
             ++session->streams_done;
-            update_child_status(session, SERVER_BUSY_WRITE, "done", stream);
             break;
         default:
             break;
index 2c8f334cce0d779fad3252ba8d953e2c8c526d5c..7932a9e2ccfed57cf111f77880daf3b359b60dce 100644 (file)
@@ -29,6 +29,7 @@
  */
 
 #include "h2.h"
+#include "h2_util.h"
 
 struct apr_thread_mutext_t;
 struct apr_thread_cond_t;
@@ -118,6 +119,8 @@ typedef struct h2_session {
     struct h2_iqueue *out_c1_blocked;  /* all streams with output blocked on c1 buffer full */
     struct h2_iqueue *ready_to_process;  /* all streams ready for processing */
 
+    h2_hd_scratch hd_scratch;
+
 } h2_session;
 
 const char *h2_session_state_str(h2_session_state state);
index 35b53860c03641033ae9d7758bcf068effadb8cb..f82140194047bff9e63d4e5288615f15b7e014cc 100644 (file)
@@ -659,7 +659,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, 
@@ -691,13 +692,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,
-                                size_t max_field_len, int *pwas_added)
+                                h2_hd_scratch *scratch)
 {
     conn_rec *c = stream->session->c1;
-    char *hname, *hvalue;
     const char *existing;
 
-    *pwas_added = 0;
     if (nlen == 0 || name[0] == ':') {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c, 
                       H2_STRM_LOG(APLOGNO(03060), stream, 
@@ -710,20 +709,35 @@ static apr_status_t add_trailer(h2_stream *stream,
     if (!stream->trailers_in) {
         stream->trailers_in = apr_table_make(stream->pool, 5);
     }
-    hname = apr_pstrndup(stream->pool, name, nlen);
-    h2_util_camel_case_header(hname, nlen);
-    existing = apr_table_get(stream->trailers_in, hname);
-    if (max_field_len 
-        && ((existing? strlen(existing)+2 : 0) + vlen + nlen + 2 > max_field_len)) {
-        /* "key: (oldval, )?nval" is too long */
+
+    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_in, 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_in, scratch->name, scratch->value);
     }
-    if (!existing) *pwas_added = 1;
-    hvalue = apr_pstrndup(stream->pool, value, vlen);
-    apr_table_mergen(stream->trailers_in, hname, hvalue);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, 
-                  H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue);
-    
+    else {
+        h2_util_camel_case_header(scratch->name, nlen);
+        apr_table_set(stream->trailers_in, 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;
 }
 
@@ -732,7 +746,7 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
                                   const char *value, size_t vlen)
 {
     h2_session *session = stream->session;
-    int error = 0, was_added = 0;
+    int error = 0;
     apr_status_t status = APR_SUCCESS;
     
     H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
@@ -760,6 +774,7 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
         ++stream->request_headers_added;
     }
     else if (H2_SS_IDLE == stream->state) {
+        int was_added;
         if (!stream->rtmp) {
             if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
                 ++stream->session->remote.emitted_count;
@@ -771,7 +786,7 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
         }
         status = h2_request_add_header(stream->rtmp, stream->pool,
                                        name, nlen, value, vlen,
-                                       session->s->limit_req_fieldsize, &was_added);
+                                       &session->hd_scratch, &was_added);
         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
                       H2_STRM_MSG(stream, "add_header: '%.*s: %.*s"),
                       (int)nlen, name, (int)vlen, value);
@@ -779,8 +794,8 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
     }
     else if (H2_SS_OPEN == stream->state) {
         status = add_trailer(stream, name, nlen, value, vlen,
-                             session->s->limit_req_fieldsize, &was_added);
-        if (was_added) ++stream->request_headers_added;
+                             &session->hd_scratch);
+        if (!status) ++stream->request_headers_added;
     }
     else {
         status = APR_EINVAL;
@@ -789,16 +804,17 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
     
     if (APR_EINVAL == status) {
         /* header too long */
-        if (!h2_stream_is_ready(stream)) {
+        if (!h2_stream_is_ready(stream) && !stream->request_headers_failed) {
             ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
-                          H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
-                                      "LimitRequestFieldSize: %.*s"),
+                          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 */
@@ -810,12 +826,13 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
         if (!h2_stream_is_ready(stream)) {
             ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c1,
                           H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
-                                      "exceeds LimitRequestFields"));
+                                      "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;
index 8e53cebdf92a586b61e1046ca523d9ada7be8e14..605c348ca127fef657e55bc7ebe947ed1ed04578 100644 (file)
@@ -1693,10 +1693,9 @@ int h2_ignore_resp_trailer(const char *name, size_t len)
 }
 
 static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
-                                   nghttp2_nv *nv, size_t max_field_len,
+                                   nghttp2_nv *nv, h2_hd_scratch *scratch,
                                    int *pwas_added)
 {
-    char *hname, *hvalue;
     const char *existing;
 
     *pwas_added = 0;
@@ -1712,15 +1711,14 @@ static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
             /* Cookie header come separately in HTTP/2, but need
              * to be merged by "; " (instead of default ", ")
              */
-            if (max_field_len
-                && strlen(existing) + nv->valuelen + nv->namelen + 4
-                   > max_field_len) {
+            if ((strlen(existing) + nv->valuelen + nv->namelen + 4)
+                   > scratch->max_len) {
                 /* "key: oldval, nval" is too long */
                 return APR_EINVAL;
             }
-            hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
             apr_table_setn(headers, "Cookie",
-                           apr_psprintf(pool, "%s; %s", existing, hvalue));
+                           apr_psprintf(pool, "%s; %.*s", existing,
+                                        (int)nv->valuelen, nv->value));
             return APR_SUCCESS;
         }
     }
@@ -1731,27 +1729,40 @@ static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
         }
     }
 
-    hname = apr_pstrndup(pool, (const char*)nv->name, nv->namelen);
-    h2_util_camel_case_header(hname, nv->namelen);
-    existing = apr_table_get(headers, hname);
-    if (max_field_len) {
-        if ((existing? strlen(existing)+2 : 0) + nv->valuelen + nv->namelen + 2
-            > max_field_len) {
-            /* "key: (oldval, )?nval" is too long */
+    if (((nv->namelen + nv->valuelen + 2) > scratch->max_len))
+        return APR_EINVAL;
+
+    /* We need 0-terminated strings to operate on apr_table */
+    AP_DEBUG_ASSERT(nv->namelen < scratch->max_len);
+    memcpy(scratch->name, nv->name, nv->namelen);
+    scratch->name[nv->namelen] = 0;
+    AP_DEBUG_ASSERT(nv->valuelen < scratch->max_len);
+    memcpy(scratch->value, nv->value, nv->valuelen);
+    scratch->value[nv->valuelen] = 0;
+
+    *pwas_added = 1;
+    existing = apr_table_get(headers, scratch->name);
+    if (existing) {
+        if (!nv->valuelen) /* not adding a 0-length value to existing */
+            return APR_SUCCESS;
+        if ((strlen(existing) + 2 + nv->valuelen + nv->namelen + 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, nv->namelen);
+        apr_table_set(headers, scratch->name, scratch->value);
     }
-    if (!existing) *pwas_added = 1;
-    hvalue = apr_pstrndup(pool, (const char*)nv->value, nv->valuelen);
-    apr_table_mergen(headers, hname, hvalue);
-
     return APR_SUCCESS;
 }
 
 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,
-                              size_t max_field_len, int *pwas_added)
+                              h2_hd_scratch *scratch, int *pwas_added)
 {
     nghttp2_nv nv;
 
@@ -1759,7 +1770,7 @@ apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool,
     nv.namelen = nlen;
     nv.value = (uint8_t*)value;
     nv.valuelen = vlen;
-    return req_add_header(headers, pool, &nv, max_field_len, pwas_added);
+    return req_add_header(headers, pool, &nv, scratch, pwas_added);
 }
 
 /*******************************************************************************
index d2e6548ba879c91e4111edc10eebcf1d0c6be501..c2cab4afa4547cb127997489ae9a4c470c8dc7df 100644 (file)
@@ -397,14 +397,21 @@ apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
                                     const struct h2_request *req);
 #endif
 
+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;
+
 /**
  * Add a HTTP/2 header and return the table key if it really was added
  * and not ignored.
  */
-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,
-                               size_t max_field_len, int *pwas_added);
+                               h2_hd_scratch *scratch, int *pwas_added);
 
 /*******************************************************************************
  * apr brigade helpers
index 6b73301c282f0db0d834a3e31662b3542ad35d1e..1687e3d981890c9515b720b6dd0b2ebb15c409bc 100644 (file)
@@ -133,7 +133,7 @@ class TestInvalidHeaders:
         assert 431 == r.response["status"]
 
     # test header field count, LimitRequestFields (default 100)
-    # see #201: several headers with same name are mered and count only once
+    # see #201: several headers with same name are merged and counted
     def test_h2_200_12(self, env):
         url = env.mkurl("https", "cgi", "/")
         opt = []
@@ -143,7 +143,7 @@ class TestInvalidHeaders:
         r = env.curl_get(url, options=opt)
         assert r.response["status"] == 200
         r = env.curl_get(url, options=(opt + ["-H", "y: 2"]))
-        assert r.response["status"] == 200
+        assert r.response["status"] == 431
 
     # test header field count, LimitRequestFields (default 100)
     # different header names count each