nghttpx: Limit header fields from backend
This commit is contained in:
parent
95ca4f55d5
commit
eec409dba7
|
@ -107,7 +107,9 @@ OPTIONS = [
|
|||
"add-forwarded",
|
||||
"strip-incoming-forwarded",
|
||||
"forwarded-by",
|
||||
"forwarded-for"
|
||||
"forwarded-for",
|
||||
"response-header-field-buffer",
|
||||
"max-response-header-fields"
|
||||
]
|
||||
|
||||
LOGVARS = [
|
||||
|
|
32
src/shrpx.cc
32
src/shrpx.cc
|
@ -1073,6 +1073,8 @@ void fill_default_config() {
|
|||
httpconf.no_host_rewrite = true;
|
||||
httpconf.header_field_buffer = 64_k;
|
||||
httpconf.max_header_fields = 100;
|
||||
httpconf.response_header_field_buffer = 64_k;
|
||||
httpconf.max_response_header_fields = 500;
|
||||
|
||||
auto &http2conf = mod_config()->http2;
|
||||
{
|
||||
|
@ -1772,14 +1774,28 @@ HTTP:
|
|||
--header-field-buffer=<SIZE>
|
||||
Set maximum buffer size for incoming HTTP request header
|
||||
field list. This is the sum of header name and value in
|
||||
bytes.
|
||||
bytes. If trailer fields exist, they are counted
|
||||
towards this number.
|
||||
Default: )"
|
||||
<< util::utos_unit(get_config()->http.header_field_buffer) << R"(
|
||||
--max-header-fields=<N>
|
||||
Set maximum number of incoming HTTP request header
|
||||
fields, which appear in one request or response header
|
||||
field list.
|
||||
fields. If trailer fields exist, they are counted
|
||||
towards this number.
|
||||
Default: )" << get_config()->http.max_header_fields << R"(
|
||||
--response-header-field-buffer=<SIZE>
|
||||
Set maximum buffer size for incoming HTTP response
|
||||
header field list. This is the sum of header name and
|
||||
value in bytes. If trailer fields exist, they are
|
||||
counted towards this number.
|
||||
Default: )"
|
||||
<< util::utos_unit(get_config()->http.response_header_field_buffer) << R"(
|
||||
--max-response-header-fields=<N>
|
||||
Set maximum number of incoming HTTP response header
|
||||
fields. If trailer fields exist, they are counted
|
||||
towards this number.
|
||||
Default: )" << get_config()->http.max_response_header_fields
|
||||
<< R"(
|
||||
|
||||
Debug:
|
||||
--frontend-http2-dump-request-header=<PATH>
|
||||
|
@ -2349,6 +2365,8 @@ int main(int argc, char **argv) {
|
|||
{SHRPX_OPT_STRIP_INCOMING_FORWARDED, no_argument, &flag, 98},
|
||||
{SHRPX_OPT_FORWARDED_BY, required_argument, &flag, 99},
|
||||
{SHRPX_OPT_FORWARDED_FOR, required_argument, &flag, 100},
|
||||
{SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER, required_argument, &flag, 101},
|
||||
{SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, required_argument, &flag, 102},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
int option_index = 0;
|
||||
|
@ -2778,6 +2796,14 @@ int main(int argc, char **argv) {
|
|||
// --forwarded-for
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, optarg);
|
||||
break;
|
||||
case 101:
|
||||
// --response-header-field-buffer
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER, optarg);
|
||||
break;
|
||||
case 102:
|
||||
// --max-response-header-fields
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, optarg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -726,6 +726,7 @@ enum {
|
|||
SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
|
||||
SHRPX_OPTID_LOG_LEVEL,
|
||||
SHRPX_OPTID_MAX_HEADER_FIELDS,
|
||||
SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS,
|
||||
SHRPX_OPTID_MRUBY_FILE,
|
||||
SHRPX_OPTID_NO_HOST_REWRITE,
|
||||
SHRPX_OPTID_NO_LOCATION_REWRITE,
|
||||
|
@ -740,6 +741,7 @@ enum {
|
|||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||
SHRPX_OPTID_READ_BURST,
|
||||
SHRPX_OPTID_READ_RATE,
|
||||
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
|
||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
||||
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
|
||||
|
@ -1258,6 +1260,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
if (util::strieq_l("frontend-http2-window-bit", name, 25)) {
|
||||
return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS;
|
||||
}
|
||||
if (util::strieq_l("max-response-header-field", name, 25)) {
|
||||
return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if (util::strieq_l("backend-keep-alive-timeou", name, 25)) {
|
||||
|
@ -1292,6 +1297,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("response-header-field-buffe", name, 27)) {
|
||||
return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (util::strieq_l("http2-max-concurrent-stream", name, 27)) {
|
||||
return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS;
|
||||
|
@ -2007,6 +2017,12 @@ int parse_config(const char *opt, const char *optarg,
|
|||
optarg);
|
||||
case SHRPX_OPTID_MAX_HEADER_FIELDS:
|
||||
return parse_uint(&mod_config()->http.max_header_fields, opt, optarg);
|
||||
case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER:
|
||||
return parse_uint_with_unit(
|
||||
&mod_config()->http.response_header_field_buffer, opt, optarg);
|
||||
case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS:
|
||||
return parse_uint(&mod_config()->http.max_response_header_fields, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_INCLUDE: {
|
||||
if (included_set.count(optarg)) {
|
||||
LOG(ERROR) << opt << ": " << optarg << " has already been included";
|
||||
|
|
|
@ -196,6 +196,10 @@ constexpr char SHRPX_OPT_STRIP_INCOMING_FORWARDED[] =
|
|||
"strip-incoming-forwarded";
|
||||
constexpr static char SHRPX_OPT_FORWARDED_BY[] = "forwarded-by";
|
||||
constexpr char SHRPX_OPT_FORWARDED_FOR[] = "forwarded-for";
|
||||
constexpr char SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER[] =
|
||||
"response-header-field-buffer";
|
||||
constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] =
|
||||
"max-response-header-fields";
|
||||
|
||||
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||
|
||||
|
@ -420,6 +424,8 @@ struct HttpConfig {
|
|||
StringRef server_name;
|
||||
size_t header_field_buffer;
|
||||
size_t max_header_fields;
|
||||
size_t response_header_field_buffer;
|
||||
size_t max_response_header_fields;
|
||||
bool no_via;
|
||||
bool no_location_rewrite;
|
||||
bool no_host_rewrite;
|
||||
|
|
|
@ -729,12 +729,31 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
}
|
||||
|
||||
auto &resp = downstream->response();
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
switch (frame->hd.type) {
|
||||
case NGHTTP2_HEADERS: {
|
||||
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||
!downstream->get_expect_final_response();
|
||||
|
||||
if (resp.fs.buffer_size() + namelen + valuelen >
|
||||
httpconf.response_header_field_buffer ||
|
||||
resp.fs.num_fields() >= httpconf.max_response_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream) << "Too large or many header field size="
|
||||
<< resp.fs.buffer_size() + namelen + valuelen
|
||||
<< ", num=" << resp.fs.num_fields() + 1;
|
||||
}
|
||||
|
||||
if (trailer) {
|
||||
// We don't care trailer part exceeds header size limit; just
|
||||
// discard it.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
if (trailer) {
|
||||
// just store header fields for trailer part
|
||||
resp.fs.add_trailer(name, namelen, value, valuelen,
|
||||
|
@ -763,6 +782,20 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
|
||||
auto &promised_req = promised_downstream->request();
|
||||
|
||||
// We use request header limit for PUSH_PROMISE
|
||||
if (promised_req.fs.buffer_size() + namelen + valuelen >
|
||||
httpconf.header_field_buffer ||
|
||||
promised_req.fs.num_fields() >= httpconf.max_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream)
|
||||
<< "Too large or many header field size="
|
||||
<< promised_req.fs.buffer_size() + namelen + valuelen
|
||||
<< ", num=" << promised_req.fs.num_fields() + 1;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
promised_req.fs.add_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
|
|
|
@ -605,15 +605,57 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int ensure_header_field_buffer(const Downstream *downstream,
|
||||
const HttpConfig &httpconf, size_t len) {
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (resp.fs.buffer_size() + len > httpconf.response_header_field_buffer) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream) << "Too large header header field size="
|
||||
<< resp.fs.buffer_size() + len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int ensure_max_header_fields(const Downstream *downstream,
|
||||
const HttpConfig &httpconf) {
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (resp.fs.num_fields() >= httpconf.max_response_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream)
|
||||
<< "Too many header field num=" << resp.fs.num_fields() + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||
auto downstream = static_cast<Downstream *>(htp->data);
|
||||
auto &resp = downstream->response();
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
||||
if (resp.fs.header_key_prev()) {
|
||||
resp.fs.append_last_header_key(data, len);
|
||||
} else {
|
||||
if (ensure_max_header_fields(downstream, httpconf) != 0) {
|
||||
return -1;
|
||||
}
|
||||
resp.fs.add_header(std::string(data, len), "");
|
||||
}
|
||||
} else {
|
||||
|
@ -621,6 +663,12 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
|||
if (resp.fs.trailer_key_prev()) {
|
||||
resp.fs.append_last_trailer_key(data, len);
|
||||
} else {
|
||||
if (ensure_max_header_fields(downstream, httpconf) != 0) {
|
||||
// Could not ignore this trailer field easily, since we may
|
||||
// get its value in htp_hdr_valcb, and it will be added to
|
||||
// wrong place or crash if trailer fields are currently empty.
|
||||
return -1;
|
||||
}
|
||||
resp.fs.add_trailer(std::string(data, len), "");
|
||||
}
|
||||
}
|
||||
|
@ -632,6 +680,11 @@ namespace {
|
|||
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||
auto downstream = static_cast<Downstream *>(htp->data);
|
||||
auto &resp = downstream->response();
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
||||
resp.fs.append_last_header_value(data, len);
|
||||
|
|
Loading…
Reference in New Issue