diff --git a/src/shrpx.cc b/src/shrpx.cc index 6d9e1a7a..49593e45 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -912,6 +912,8 @@ void fill_default_config() { mod_config()->fetch_ocsp_response_file = strcopy(PKGDATADIR "/fetch-ocsp-response"); mod_config()->no_ocsp = false; + mod_config()->header_field_buffer = 64 * 1024; + mod_config()->max_header_fields = 100; } } // namespace @@ -1336,6 +1338,16 @@ HTTP: won't replace anything already set. This option can be used several times to specify multiple header fields. Example: --add-response-header="foo: bar" + --header-field-buffer= + Set maximum buffer size for incoming HTTP header field + list. This is the sum of header name and value in + bytes. + Default: )" + << util::utos_with_unit(get_config()->header_field_buffer) << R"( + --max-header-fields= + Set maximum number of incoming HTTP header fields, which + appear in one request or response header field list. + Default: )" << get_config()->max_header_fields << R"( Debug: --frontend-http2-dump-request-header= @@ -1496,6 +1508,8 @@ int main(int argc, char **argv) { {"fetch-ocsp-response-file", required_argument, &flag, 77}, {"ocsp-update-interval", required_argument, &flag, 78}, {"no-ocsp", no_argument, &flag, 79}, + {"header-field-buffer", required_argument, &flag, 80}, + {"max-header-fields", required_argument, &flag, 81}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -1846,6 +1860,14 @@ int main(int argc, char **argv) { // --no-ocsp cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, "yes"); break; + case 80: + // --header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, optarg); + break; + case 81: + // --max-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, optarg); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 2fc93ec9..9e0ac424 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -150,6 +150,8 @@ const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[] = const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[] = "fetch-ocsp-response-file"; const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[] = "ocsp-update-interval"; const char SHRPX_OPT_NO_OCSP[] = "no-ocsp"; +const char SHRPX_OPT_HEADER_FIELD_BUFFER[] = "header-field-buffer"; +const char SHRPX_OPT_MAX_HEADER_FIELDS[] = "max-header-fields"; namespace { Config *config = nullptr; @@ -1212,6 +1214,15 @@ int parse_config(const char *opt, const char *optarg) { return 0; } + if (util::strieq(opt, SHRPX_OPT_HEADER_FIELD_BUFFER)) { + return parse_uint_with_unit(&mod_config()->header_field_buffer, opt, + optarg); + } + + if (util::strieq(opt, SHRPX_OPT_MAX_HEADER_FIELDS)) { + return parse_uint(&mod_config()->max_header_fields, opt, optarg); + } + if (util::strieq(opt, "conf")) { LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index d3c46a5b..4856fb8e 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -139,6 +139,8 @@ extern const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[]; extern const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[]; extern const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[]; extern const char SHRPX_OPT_NO_OCSP[]; +extern const char SHRPX_OPT_HEADER_FIELD_BUFFER[]; +extern const char SHRPX_OPT_MAX_HEADER_FIELDS[]; union sockaddr_union { sockaddr_storage storage; @@ -282,6 +284,8 @@ struct Config { size_t rlimit_nofile; size_t downstream_request_buffer_size; size_t downstream_response_buffer_size; + size_t header_field_buffer; + size_t max_header_fields; // Bit mask to disable SSL/TLS protocol versions. This will be // passed to SSL_CTX_set_options(). long int tls_proto_mask; diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 718c8bd2..72276090 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -286,9 +286,6 @@ public: // Change the priority of downstream int change_priority(int32_t pri); - // Maximum buffer size for header name/value pairs. - static constexpr size_t MAX_HEADERS_SUM = 128 * 1024; - bool get_rst_stream_after_end_stream() const; void set_rst_stream_after_end_stream(bool f); diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 89e12dd6..2e8568ed 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -733,10 +733,15 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE && !downstream->get_expect_final_response(); - if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { + if (downstream->get_response_headers_sum() + namelen + valuelen > + get_config()->header_field_buffer || + downstream->get_response_headers().size() >= + get_config()->max_header_fields) { if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "Too large header block size=" - << downstream->get_response_headers_sum(); + DLOG(INFO, downstream) + << "Too large or many header field size=" + << downstream->get_response_headers_sum() + namelen + valuelen + << ", num=" << downstream->get_response_headers().size() + 1; } if (trailer) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index fcb91829..f14adcd6 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -202,14 +202,19 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { + if (downstream->get_request_headers_sum() + namelen + valuelen > + get_config()->header_field_buffer || + downstream->get_request_headers().size() >= + get_config()->max_header_fields) { if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { return 0; } if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Too large header block size=" - << downstream->get_request_headers_sum(); + ULOG(INFO, upstream) << "Too large or many header field size=" + << downstream->get_request_headers_sum() + namelen + + valuelen << ", num=" + << downstream->get_request_headers().size() + 1; } // just ignore header fields if this is trailer part. diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 329fdce5..6e5c5295 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -582,10 +582,29 @@ int htp_hdrs_completecb(http_parser *htp) { namespace { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { auto downstream = static_cast(htp->data); + + if (downstream->get_response_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "Too large header block size=" + << downstream->get_response_headers_sum() + len; + } + return -1; + } + if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_header_key_prev()) { downstream->append_last_response_header_key(data, len); } else { + if (downstream->get_response_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too many header field num=" + << downstream->get_response_headers().size() + 1; + } + return -1; + } downstream->add_response_header(std::string(data, len), ""); } } else { @@ -593,16 +612,18 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { if (downstream->get_response_trailer_key_prev()) { downstream->append_last_response_trailer_key(data, len); } else { + if (downstream->get_response_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too many header field num=" + << downstream->get_response_headers().size() + 1; + } + return -1; + } downstream->add_response_trailer(std::string(data, len), ""); } } - if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "Too large header block size=" - << downstream->get_response_headers_sum(); - } - return -1; - } return 0; } } // namespace @@ -610,6 +631,14 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { namespace { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { auto downstream = static_cast(htp->data); + if (downstream->get_response_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "Too large header block size=" + << downstream->get_response_headers_sum() + len; + } + return -1; + } if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_header_key_prev()) { downstream->set_last_response_header_value(data, len); @@ -623,13 +652,6 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { downstream->append_last_response_trailer_value(data, len); } } - if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "Too large header block size=" - << downstream->get_response_headers_sum(); - } - return -1; - } return 0; } } // namespace diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 6a6ed2f2..ca5d3a08 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -87,10 +87,26 @@ namespace { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { auto upstream = static_cast(htp->data); auto downstream = upstream->get_downstream(); + if (downstream->get_request_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large header block size=" + << downstream->get_request_headers_sum() + len; + } + return -1; + } if (downstream->get_request_state() == Downstream::INITIAL) { if (downstream->get_request_header_key_prev()) { downstream->append_last_request_header_key(data, len); } else { + if (downstream->get_request_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too many header field num=" + << downstream->get_request_headers().size() + 1; + } + return -1; + } downstream->add_request_header(std::string(data, len), ""); } } else { @@ -98,16 +114,17 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { if (downstream->get_request_trailer_key_prev()) { downstream->append_last_request_trailer_key(data, len); } else { + if (downstream->get_request_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too many header field num=" + << downstream->get_request_headers().size() + 1; + } + return -1; + } downstream->add_request_trailer(std::string(data, len), ""); } } - if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Too large header block size=" - << downstream->get_request_headers_sum(); - } - return -1; - } return 0; } } // namespace @@ -116,6 +133,14 @@ namespace { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { auto upstream = static_cast(htp->data); auto downstream = upstream->get_downstream(); + if (downstream->get_request_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large header block size=" + << downstream->get_request_headers_sum() + len; + } + return -1; + } if (downstream->get_request_state() == Downstream::INITIAL) { if (downstream->get_request_header_key_prev()) { downstream->set_last_request_header_value(data, len); @@ -129,13 +154,6 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { downstream->append_last_request_trailer_value(data, len); } } - if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Too large header block size=" - << downstream->get_request_headers_sum(); - } - return -1; - } return 0; } } // namespace