nghttpx: Limit header fields from backend
This commit is contained in:
parent
95ca4f55d5
commit
eec409dba7
|
@ -107,7 +107,9 @@ OPTIONS = [
|
||||||
"add-forwarded",
|
"add-forwarded",
|
||||||
"strip-incoming-forwarded",
|
"strip-incoming-forwarded",
|
||||||
"forwarded-by",
|
"forwarded-by",
|
||||||
"forwarded-for"
|
"forwarded-for",
|
||||||
|
"response-header-field-buffer",
|
||||||
|
"max-response-header-fields"
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGVARS = [
|
LOGVARS = [
|
||||||
|
|
32
src/shrpx.cc
32
src/shrpx.cc
|
@ -1073,6 +1073,8 @@ void fill_default_config() {
|
||||||
httpconf.no_host_rewrite = true;
|
httpconf.no_host_rewrite = true;
|
||||||
httpconf.header_field_buffer = 64_k;
|
httpconf.header_field_buffer = 64_k;
|
||||||
httpconf.max_header_fields = 100;
|
httpconf.max_header_fields = 100;
|
||||||
|
httpconf.response_header_field_buffer = 64_k;
|
||||||
|
httpconf.max_response_header_fields = 500;
|
||||||
|
|
||||||
auto &http2conf = mod_config()->http2;
|
auto &http2conf = mod_config()->http2;
|
||||||
{
|
{
|
||||||
|
@ -1772,14 +1774,28 @@ HTTP:
|
||||||
--header-field-buffer=<SIZE>
|
--header-field-buffer=<SIZE>
|
||||||
Set maximum buffer size for incoming HTTP request header
|
Set maximum buffer size for incoming HTTP request header
|
||||||
field list. This is the sum of header name and value in
|
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: )"
|
Default: )"
|
||||||
<< util::utos_unit(get_config()->http.header_field_buffer) << R"(
|
<< util::utos_unit(get_config()->http.header_field_buffer) << R"(
|
||||||
--max-header-fields=<N>
|
--max-header-fields=<N>
|
||||||
Set maximum number of incoming HTTP request header
|
Set maximum number of incoming HTTP request header
|
||||||
fields, which appear in one request or response header
|
fields. If trailer fields exist, they are counted
|
||||||
field list.
|
towards this number.
|
||||||
Default: )" << get_config()->http.max_header_fields << R"(
|
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:
|
Debug:
|
||||||
--frontend-http2-dump-request-header=<PATH>
|
--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_STRIP_INCOMING_FORWARDED, no_argument, &flag, 98},
|
||||||
{SHRPX_OPT_FORWARDED_BY, required_argument, &flag, 99},
|
{SHRPX_OPT_FORWARDED_BY, required_argument, &flag, 99},
|
||||||
{SHRPX_OPT_FORWARDED_FOR, required_argument, &flag, 100},
|
{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}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -2778,6 +2796,14 @@ int main(int argc, char **argv) {
|
||||||
// --forwarded-for
|
// --forwarded-for
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, optarg);
|
cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, optarg);
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -726,6 +726,7 @@ enum {
|
||||||
SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
|
SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
|
||||||
SHRPX_OPTID_LOG_LEVEL,
|
SHRPX_OPTID_LOG_LEVEL,
|
||||||
SHRPX_OPTID_MAX_HEADER_FIELDS,
|
SHRPX_OPTID_MAX_HEADER_FIELDS,
|
||||||
|
SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS,
|
||||||
SHRPX_OPTID_MRUBY_FILE,
|
SHRPX_OPTID_MRUBY_FILE,
|
||||||
SHRPX_OPTID_NO_HOST_REWRITE,
|
SHRPX_OPTID_NO_HOST_REWRITE,
|
||||||
SHRPX_OPTID_NO_LOCATION_REWRITE,
|
SHRPX_OPTID_NO_LOCATION_REWRITE,
|
||||||
|
@ -740,6 +741,7 @@ enum {
|
||||||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||||
SHRPX_OPTID_READ_BURST,
|
SHRPX_OPTID_READ_BURST,
|
||||||
SHRPX_OPTID_READ_RATE,
|
SHRPX_OPTID_READ_RATE,
|
||||||
|
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
|
||||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||||
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
||||||
SHRPX_OPTID_STREAM_WRITE_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)) {
|
if (util::strieq_l("frontend-http2-window-bit", name, 25)) {
|
||||||
return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS;
|
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;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
if (util::strieq_l("backend-keep-alive-timeou", name, 25)) {
|
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;
|
return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'r':
|
||||||
|
if (util::strieq_l("response-header-field-buffe", name, 27)) {
|
||||||
|
return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
if (util::strieq_l("http2-max-concurrent-stream", name, 27)) {
|
if (util::strieq_l("http2-max-concurrent-stream", name, 27)) {
|
||||||
return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS;
|
return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS;
|
||||||
|
@ -2007,6 +2017,12 @@ int parse_config(const char *opt, const char *optarg,
|
||||||
optarg);
|
optarg);
|
||||||
case SHRPX_OPTID_MAX_HEADER_FIELDS:
|
case SHRPX_OPTID_MAX_HEADER_FIELDS:
|
||||||
return parse_uint(&mod_config()->http.max_header_fields, opt, optarg);
|
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: {
|
case SHRPX_OPTID_INCLUDE: {
|
||||||
if (included_set.count(optarg)) {
|
if (included_set.count(optarg)) {
|
||||||
LOG(ERROR) << opt << ": " << optarg << " has already been included";
|
LOG(ERROR) << opt << ": " << optarg << " has already been included";
|
||||||
|
|
|
@ -196,6 +196,10 @@ constexpr char SHRPX_OPT_STRIP_INCOMING_FORWARDED[] =
|
||||||
"strip-incoming-forwarded";
|
"strip-incoming-forwarded";
|
||||||
constexpr static char SHRPX_OPT_FORWARDED_BY[] = "forwarded-by";
|
constexpr static char SHRPX_OPT_FORWARDED_BY[] = "forwarded-by";
|
||||||
constexpr char SHRPX_OPT_FORWARDED_FOR[] = "forwarded-for";
|
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;
|
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -420,6 +424,8 @@ struct HttpConfig {
|
||||||
StringRef server_name;
|
StringRef server_name;
|
||||||
size_t header_field_buffer;
|
size_t header_field_buffer;
|
||||||
size_t max_header_fields;
|
size_t max_header_fields;
|
||||||
|
size_t response_header_field_buffer;
|
||||||
|
size_t max_response_header_fields;
|
||||||
bool no_via;
|
bool no_via;
|
||||||
bool no_location_rewrite;
|
bool no_location_rewrite;
|
||||||
bool no_host_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 &resp = downstream->response();
|
||||||
|
auto &httpconf = get_config()->http;
|
||||||
|
|
||||||
switch (frame->hd.type) {
|
switch (frame->hd.type) {
|
||||||
case NGHTTP2_HEADERS: {
|
case NGHTTP2_HEADERS: {
|
||||||
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||||
!downstream->get_expect_final_response();
|
!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) {
|
if (trailer) {
|
||||||
// just store header fields for trailer part
|
// just store header fields for trailer part
|
||||||
resp.fs.add_trailer(name, namelen, value, valuelen,
|
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();
|
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);
|
auto token = http2::lookup_token(name, namelen);
|
||||||
promised_req.fs.add_header(name, namelen, value, valuelen,
|
promised_req.fs.add_header(name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||||
|
|
|
@ -605,15 +605,57 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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 {
|
namespace {
|
||||||
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
auto downstream = static_cast<Downstream *>(htp->data);
|
auto downstream = static_cast<Downstream *>(htp->data);
|
||||||
auto &resp = downstream->response();
|
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 (downstream->get_response_state() == Downstream::INITIAL) {
|
||||||
if (resp.fs.header_key_prev()) {
|
if (resp.fs.header_key_prev()) {
|
||||||
resp.fs.append_last_header_key(data, len);
|
resp.fs.append_last_header_key(data, len);
|
||||||
} else {
|
} else {
|
||||||
|
if (ensure_max_header_fields(downstream, httpconf) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
resp.fs.add_header(std::string(data, len), "");
|
resp.fs.add_header(std::string(data, len), "");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -621,6 +663,12 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
if (resp.fs.trailer_key_prev()) {
|
if (resp.fs.trailer_key_prev()) {
|
||||||
resp.fs.append_last_trailer_key(data, len);
|
resp.fs.append_last_trailer_key(data, len);
|
||||||
} else {
|
} 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), "");
|
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) {
|
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||||
auto downstream = static_cast<Downstream *>(htp->data);
|
auto downstream = static_cast<Downstream *>(htp->data);
|
||||||
auto &resp = downstream->response();
|
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 (downstream->get_response_state() == Downstream::INITIAL) {
|
||||||
resp.fs.append_last_header_value(data, len);
|
resp.fs.append_last_header_value(data, len);
|
||||||
|
|
Loading…
Reference in New Issue