From: Stefan Eissing Date: Thu, 14 Aug 2025 07:53:10 +0000 (+0000) Subject: *) mod_http2: update to version 2.0.35 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=149f6c72d3a914beb28dabb060de1dc90e0ad520;p=thirdparty%2Fapache%2Fhttpd.git *) 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. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1927792 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/changes-entries/h2_v2.0.35.txt b/changes-entries/h2_v2.0.35.txt new file mode 100644 index 0000000000..288f6e1f17 --- /dev/null +++ b/changes-entries/h2_v2.0.35.txt @@ -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] diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index 676f4b2070..e7f7e68644 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -1168,4 +1168,31 @@ H2EarlyHint Link "</my.css>;rel=preload;as=style" + + H2MaxStreamErrors + Maximum amount of client caused errors to tolerate + H2MaxStreamErrors n + H2MaxStreamErrors 8 + + server config + virtual host + + Available in version 2.5.1 and later. + + +

+ H2MaxStreamErrors 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. +

+

+ Set to 0 to tolerate faulty clients. +

+
+
+ diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index 51a2c24745..94fd8d2242 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -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, diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h index 7f3158f6c8..87fc0b18b6 100644 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@ -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; diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index ffc83ed380..470a14ee61 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -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. */ diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index a5f1872bc2..21ede5c100 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -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)) { diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index 7932a9e2cc..497f7e1936 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -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 */ diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index 685e771eaa..2b2375a1d9 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -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) { diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index c2e96500cf..8d38c34e78 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -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 */