]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) mod_http2: update to version 2.0.35
authorStefan Eissing <icing@apache.org>
Thu, 14 Aug 2025 07:53:10 +0000 (07:53 +0000)
committerStefan Eissing <icing@apache.org>
Thu, 14 Aug 2025 07:53:10 +0000 (07:53 +0000)
     New directive `H2MaxStreamErrors` to control how much bad behaviour
     by clients is tolerated before the connection is closed.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1927792 13f79535-47bb-0310-9956-ffa450edef68

changes-entries/h2_v2.0.35.txt [new file with mode: 0644]
docs/manual/mod/mod_http2.xml
modules/http2/h2_config.c
modules/http2/h2_config.h
modules/http2/h2_mplx.c
modules/http2/h2_session.c
modules/http2/h2_session.h
modules/http2/h2_util.c
modules/http2/h2_version.h

diff --git a/changes-entries/h2_v2.0.35.txt b/changes-entries/h2_v2.0.35.txt
new file mode 100644 (file)
index 0000000..288f6e1
--- /dev/null
@@ -0,0 +1,4 @@
+  *) mod_http2: update to version 2.0.35
+     New directive `H2MaxStreamErrors` to control how much bad behaviour
+     by clients is tolerated before the connection is closed.
+     [Stefan Eissing]
index 676f4b207081763dc6d0e9da4fedf2bead747e27..e7f7e68644293a16ae1439da8899c6f8eaa481ba 100644 (file)
@@ -1168,4 +1168,31 @@ H2EarlyHint Link "&lt;/my.css&gt;;rel=preload;as=style"
         </usage>
     </directivesynopsis>
 
+        <directivesynopsis>
+        <name>H2MaxStreamErrors</name>
+        <description>Maximum amount of client caused errors to tolerate</description>
+        <syntax>H2MaxStreamErrors <em>n</em></syntax>
+        <default>H2MaxStreamErrors 8</default>
+        <contextlist>
+            <context>server config</context>
+            <context>virtual host</context>
+        </contextlist>
+        <compatibility>Available in version 2.5.1 and later.</compatibility>
+
+        <usage>
+            <p>
+                <directive>H2MaxStreamErrors</directive> sets the maxmimum amount
+                of tolerated HTTP/2 stream errors caused by the client.
+                When exceeding this limit, the connection will be closed.
+                Stream errors are protocol violations on an individual HTTP/2
+                stream that do not necessitate a connection close by the
+                protocol specification, but can be a sign of malicious
+                activity by a client.
+            </p>
+            <p>
+                Set to 0 to tolerate faulty clients.
+            </p>
+        </usage>
+    </directivesynopsis>
+
 </modulesynopsis>
index 51a2c24745cea161707fa9ddf239be370f13df08..94fd8d22420f52c56f4f0ae27ae6b2d76b827273 100644 (file)
@@ -78,6 +78,7 @@ typedef struct h2_config {
     apr_interval_time_t stream_timeout;/* beam timeout */
     int max_data_frame_len;          /* max # bytes in a single h2 DATA frame */
     int max_hd_block_len;            /* max # bytes in a response header block */
+    int max_stream_errors;           /* max # of tolerated stream errors */
     int proxy_requests;              /* act as forward proxy */
     int h2_websockets;               /* if mod_h2 negotiating WebSockets */
 } h2_config;
@@ -119,6 +120,7 @@ static h2_config defconf = {
     -1,                     /* beam timeout */
     0,                      /* max DATA frame len, 0 == no extra limit */
     0,                      /* max header block len, 0 == no extra limit */
+    8,                      /* max stream errors tolerated */
     0,                      /* forward proxy */
     0,                      /* WebSockets negotiation, enabled */
 };
@@ -168,6 +170,7 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
     conf->stream_timeout       = DEF_VAL;
     conf->max_data_frame_len   = DEF_VAL;
     conf->max_hd_block_len     = DEF_VAL;
+    conf->max_stream_errors    = DEF_VAL;
     conf->proxy_requests       = DEF_VAL;
     conf->h2_websockets        = DEF_VAL;
     return conf;
@@ -220,6 +223,7 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
     n->stream_timeout       = H2_CONFIG_GET(add, base, stream_timeout);
     n->max_data_frame_len   = H2_CONFIG_GET(add, base, max_data_frame_len);
     n->max_hd_block_len     = H2_CONFIG_GET(add, base, max_hd_block_len);
+    n->max_stream_errors    = H2_CONFIG_GET(add, base, max_stream_errors);
     n->proxy_requests       = H2_CONFIG_GET(add, base, proxy_requests);
     n->h2_websockets        = H2_CONFIG_GET(add, base, h2_websockets);
     return n;
@@ -319,6 +323,9 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
             return H2_CONFIG_GET(conf, &defconf, h2_websockets);
         case H2_CONF_MAX_HEADER_BLOCK_LEN:
             return H2_CONFIG_GET(conf, &defconf, max_hd_block_len);
+        case H2_CONF_MAX_STREAM_ERRORS:
+            return H2_CONFIG_GET(conf, &defconf, max_stream_errors);
+
         default:
             return DEF_VAL;
     }
@@ -389,6 +396,9 @@ static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
             break;
         case H2_CONF_MAX_HEADER_BLOCK_LEN:
             H2_CONFIG_SET(conf, max_hd_block_len, val);
+            break;
+        case H2_CONF_MAX_STREAM_ERRORS:
+            H2_CONFIG_SET(conf, max_stream_errors, val);
         default:
             break;
     }
@@ -669,6 +679,17 @@ static const char *h2_conf_set_max_hd_block_len(cmd_parms *cmd,
     return NULL;
 }
 
+static const char *h2_conf_set_max_stream_errors(cmd_parms *cmd,
+                                               void *dirconf, const char *value)
+{
+    int val = (int)apr_atoi64(value);
+    if (val < 0) {
+        return "value must be 0 or larger";
+    }
+    CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_STREAM_ERRORS, val);
+    return NULL;
+}
+
 static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
                                                    void *dirconf, const char *value)
 {
@@ -1092,6 +1113,8 @@ const command_rec h2_cmds[] = {
                   RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
     AP_INIT_TAKE1("H2MaxHeaderBlockLen", h2_conf_set_max_hd_block_len, NULL,
                   RSRC_CONF, "maximum number of bytes in a response header block"),
+    AP_INIT_TAKE1("H2MaxStreamErrors", h2_conf_set_max_stream_errors, NULL,
+                  RSRC_CONF, "maximum number of flow control errors tolerated"),
     AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
                    OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
     AP_INIT_TAKE1("H2ProxyRequests", h2_conf_set_proxy_requests, NULL,
index 7f3158f6c85c4cbe53d32c1dec572727e433d3ca..87fc0b18b6e0bcecc83acffc469aa8665dd764ce 100644 (file)
@@ -47,6 +47,7 @@ typedef enum {
     H2_CONF_PROXY_REQUESTS,
     H2_CONF_WEBSOCKETS,
     H2_CONF_MAX_HEADER_BLOCK_LEN,
+    H2_CONF_MAX_STREAM_ERRORS,
 } h2_config_var_t;
 
 struct apr_hash_t;
index ffc83ed38016638b0d55e3031798371a47ab2ad2..470a14ee61baf3e6a7b0ec85efb3a65ba79b748a 100644 (file)
@@ -1086,8 +1086,9 @@ static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx)
             m->last_mood_change = now;
             m->irritations_since = 0;
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                          H2_MPLX_MSG(m, "mood update, increasing worker limit to %d"),
-                          m->processing_limit);
+                          H2_MPLX_MSG(m, "mood update, increasing worker limit"
+                          "to %d, processing %d right now"),
+                          m->processing_limit, m->processing_count);
         }
     }
 }
@@ -1116,8 +1117,9 @@ static void m_be_annoyed(h2_mplx *m)
             m->last_mood_change = now;
             m->irritations_since = 0;
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
-                          H2_MPLX_MSG(m, "mood update, decreasing worker limit to %d"),
-                          m->processing_limit);
+                          H2_MPLX_MSG(m, "mood update, decreasing worker limit "
+                          "to %d, processing %d right now"),
+                          m->processing_limit, m->processing_count);
         }
     }
 }
@@ -1141,6 +1143,7 @@ static int reset_is_acceptable(h2_stream *stream)
      * The responses to such requests continue forever otherwise.
      *
      */
+    if (stream->rst_error) return 0; /* errored stream. bad. */
     if (!stream_is_running(stream)) return 1;
     if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
     if (!stream->response) return 0; /* no response headers produced yet. bad. */
index a5f1872bc203946323ba8c579ed69bdeeeec318c..21ede5c100d2e65249862c4ec312242437f9abfe 100644 (file)
@@ -61,6 +61,8 @@ static void transit(h2_session *session, const char *action,
 static void on_stream_state_enter(void *ctx, h2_stream *stream);
 static void on_stream_state_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
 static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
+static apr_status_t h2_session_shutdown(h2_session *session, int error,
+                                        const char *msg, int force_close);
 
 static int h2_session_status_from_apr_status(apr_status_t rv)
 {
@@ -290,6 +292,7 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
                           "closing with err=%d %s"), 
                           (int)error_code, h2_protocol_err_description(error_code));
             h2_stream_rst(stream, error_code);
+            h2_mplx_c1_client_rst(session->mplx, stream_id, stream);
         }
     }
     return 0;
@@ -608,7 +611,11 @@ static int on_frame_send_cb(nghttp2_session *ngh2,
             /* PUSH_PROMISE we report on the promised stream */
             stream_id = frame->push_promise.promised_stream_id;
             break;
-        default:    
+        case NGHTTP2_RST_STREAM:
+            if(frame->rst_stream.error_code == NGHTTP2_FLOW_CONTROL_ERROR)
+              ++session->stream_errors;
+            break;
+        default:
             break;
     }
     
@@ -652,10 +659,11 @@ static int on_frame_not_send_cb(nghttp2_session *ngh2,
 
     stream = get_stream(session, stream_id);
     h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c1,
-                  H2_SSSN_LOG(APLOGNO(10509), session,
-                  "not sent FRAME[%s], error %d: %s"),
-                  buffer, ngh2_err, nghttp2_strerror(ngh2_err));
+    if (!stream || !stream->rst_error)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
+                      H2_SSSN_LOG(APLOGNO(10509), session,
+                      "not sent FRAME[%s], error %d: %s"),
+                      buffer, ngh2_err, nghttp2_strerror(ngh2_err));
     if(stream) {
         h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
         return 0;
@@ -968,6 +976,7 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
     session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
     session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
     session->max_data_frame_len = h2_config_sgeti(s, H2_CONF_MAX_DATA_FRAME_LEN);
+    session->max_stream_errors = h2_config_sgeti(s, H2_CONF_MAX_STREAM_ERRORS);
 
     session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count);
     session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count);
@@ -1063,14 +1072,16 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
                                   "created, max_streams=%d, stream_mem=%d, "
                                   "workers_limit=%d, workers_max=%d, "
                                   "push_diary(type=%d,N=%d), "
-                                  "max_data_frame_len=%d"),
+                                  "max_data_frame_len=%d, "
+                                  "max_stream_errors=%d"),
                       (int)session->max_stream_count, 
                       (int)session->max_stream_mem,
                       session->mplx->processing_limit,
                       session->mplx->processing_max,
                       session->push_diary->dtype, 
                       (int)session->push_diary->N,
-                      (int)session->max_data_frame_len);
+                      (int)session->max_data_frame_len,
+                      session->max_stream_errors);
     }
     
     apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
@@ -1609,6 +1620,14 @@ static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char
     }
 }
 
+static void h2_session_ev_bad_client(h2_session *session, int arg, const char *msg)
+{
+    transit(session, msg, H2_SESSION_ST_DONE);
+    if (!session->local.shutdown) {
+        h2_session_shutdown(session, arg, msg, 1);
+    }
+}
+
 static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
 {
     h2_session_shutdown(session, arg, msg, 1);
@@ -1806,6 +1825,9 @@ void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
         case H2_SESSION_EV_NO_MORE_STREAMS:
             h2_session_ev_no_more_streams(session);
             break;
+        case H2_SESSION_EV_BAD_CLIENT:
+            h2_session_ev_bad_client(session, arg, msg);
+            break;
         default:
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
                           H2_SSSN_MSG(session, "unknown event %d"), ev);
@@ -1884,6 +1906,12 @@ apr_status_t h2_session_process(h2_session *session, int async,
             }
         }
 
+        if (session->max_stream_errors &&
+            session->stream_errors > session->max_stream_errors) {
+            h2_session_dispatch_event(session, H2_SESSION_EV_BAD_CLIENT,
+                                      NGHTTP2_PROTOCOL_ERROR, NULL);
+        }
+
         session->status[0] = '\0';
         
         if (h2_session_want_send(session)) {
index 7932a9e2ccfed57cf111f77880daf3b359b60dce..497f7e193605b3c166f355d0a7e19abfe7828353 100644 (file)
@@ -60,6 +60,7 @@ typedef enum {
     H2_SESSION_EV_MPM_STOPPING,     /* the process is stopping */
     H2_SESSION_EV_PRE_CLOSE,        /* connection will close after this */
     H2_SESSION_EV_NO_MORE_STREAMS,  /* no more streams to process */
+    H2_SESSION_EV_BAD_CLIENT,       /* client misbehaving badly */
 } h2_session_event_t;
 
 typedef struct h2_session {
@@ -98,7 +99,9 @@ typedef struct h2_session {
     unsigned int pushes_promised;   /* number of http/2 push promises submitted */
     unsigned int pushes_submitted;  /* number of http/2 pushed responses submitted */
     unsigned int pushes_reset;      /* number of http/2 pushed reset by client */
-    
+    unsigned int max_stream_errors; /* max client stream errors tolerated */
+    unsigned int stream_errors;     /* number of stream errors by client */
+
     apr_size_t frames_received;     /* number of http/2 frames received */
     apr_size_t frames_sent;         /* number of http/2 frames sent */
     
index 685e771eaa42461d1460e48f59362d904605706d..2b2375a1d908abe498fdfd1a61903a84c80f427c 100644 (file)
@@ -1805,9 +1805,11 @@ int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
         }
         case NGHTTP2_RST_STREAM: {
             return apr_snprintf(buffer, maxlen,
-                                "RST_STREAM[length=%d, flags=%d, stream=%d]",
+                                "RST_STREAM[length=%d, flags=%d, stream=%d"
+                                ",error=%d]",
                                 (int)frame->hd.length,
-                                frame->hd.flags, frame->hd.stream_id);
+                                frame->hd.flags, frame->hd.stream_id,
+                                frame->rst_stream.error_code);
         }
         case NGHTTP2_SETTINGS: {
             if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
index c2e96500cf8ef9f03267fc06d0b9f132ee89876e..8d38c34e7865cfe9e8aca8fba70e723ec00c9581 100644 (file)
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "2.0.34"
+#define MOD_HTTP2_VERSION "2.0.35"
 
 /**
  * @macro
@@ -35,7 +35,7 @@
  * release. This is a 24 bit number with 8 bits for major number, 8 bits
  * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
  */
-#define MOD_HTTP2_VERSION_NUM 0x020022
+#define MOD_HTTP2_VERSION_NUM 0x020023
 
 
 #endif /* mod_h2_h2_version_h */