diff --git a/ChangeLog b/ChangeLog index b83eda03..10983374 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ +v2.0.34 +-------------------------------------------------------------------------------- * Added support for "ProxyErrorOverride" directive in mod_proxy_http2. * Fix a bug in calculating the log2 value of integers, used in push diaries and proxy window size calculations. Apache PR69741. diff --git a/README.md b/README.md index ff3ad4e2..8801f866 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ The following configuration directives are available: * [H2MaxDataFrameLen](#h2maxdataframelen) * [H2MaxHeaderBlockLen](#h2maxheaderblocklen) * [H2MaxSessionStreams](#h2maxsessionstreams) + * [H2MaxStreamErrors](#h2maxstreamerrors) * [H2MaxWorkerIdleSeconds](#h2maxworkeridleseconds) * [H2MaxWorkers](#h2maxworkers) * [H2MinWorkers](#h2minworkers) @@ -243,6 +244,20 @@ Context: server config, virtual host ``` This directive sets the maximum number of active streams per HTTP/2 session (e.g. connection) that the server allows. A stream is active if it is not idle or closed according to RFC 7540. +### H2MaxStreamErrors +``` +Syntax: H2MaxStreamErrors n +Default: H2MaxStreamErrors 8 +Context: server config, virtual host +``` +H2MaxStreamErrors set 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. + ### H2MaxWorkerIdleSeconds ``` Syntax: H2MaxWorkerIdleSeconds n diff --git a/configure.ac b/configure.ac index 3132b5f8..3ee1a82b 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ # AC_PREREQ([2.69]) -AC_INIT([mod_http2], [2.0.33], [stefan@eissing.org]) +AC_INIT([mod_http2], [2.0.34], [stefan@eissing.org]) LT_PREREQ([2.2.6]) LT_INIT() diff --git a/mod_http2/h2_config.c b/mod_http2/h2_config.c index 51a2c247..94fd8d22 100644 --- a/mod_http2/h2_config.c +++ b/mod_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/mod_http2/h2_config.h b/mod_http2/h2_config.h index 7f3158f6..87fc0b18 100644 --- a/mod_http2/h2_config.h +++ b/mod_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/mod_http2/h2_mplx.c b/mod_http2/h2_mplx.c index ffc83ed3..470a14ee 100644 --- a/mod_http2/h2_mplx.c +++ b/mod_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/mod_http2/h2_session.c b/mod_http2/h2_session.c index a5f1872b..21ede5c1 100644 --- a/mod_http2/h2_session.c +++ b/mod_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/mod_http2/h2_session.h b/mod_http2/h2_session.h index 7932a9e2..497f7e19 100644 --- a/mod_http2/h2_session.h +++ b/mod_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/mod_http2/h2_util.c b/mod_http2/h2_util.c index 685e771e..2b2375a1 100644 --- a/mod_http2/h2_util.c +++ b/mod_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/mod_http2/h2_version.h b/mod_http2/h2_version.h index f8c44e92..b7f14319 100644 --- a/mod_http2/h2_version.h +++ b/mod_http2/h2_version.h @@ -27,7 +27,7 @@ * @macro * Version number of the http2 module as c string */ -#define MOD_HTTP2_VERSION "2.0.33-git" +#define MOD_HTTP2_VERSION "2.0.34-git" /** * @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 0x020021 +#define MOD_HTTP2_VERSION_NUM 0x020022 #endif /* mod_h2_h2_version_h */