nghttpx: Limit header fields from backend

This commit is contained in:
Tatsuhiro Tsujikawa 2016-02-06 12:25:34 +09:00
parent 95ca4f55d5
commit eec409dba7
6 changed files with 140 additions and 4 deletions

View File

@ -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 = [

View File

@ -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;
}

View File

@ -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";

View File

@ -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;

View File

@ -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);

View File

@ -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);