Merge branch 'limit-incoming-headers'

This commit is contained in:
Tatsuhiro Tsujikawa 2016-02-11 23:21:02 +09:00
commit 5ad753b90c
13 changed files with 96 additions and 3 deletions

View File

@ -1608,6 +1608,14 @@ typedef int (*nghttp2_on_begin_headers_callback)(nghttp2_session *session,
* *
* To set this callback to :type:`nghttp2_session_callbacks`, use * To set this callback to :type:`nghttp2_session_callbacks`, use
* `nghttp2_session_callbacks_set_on_header_callback()`. * `nghttp2_session_callbacks_set_on_header_callback()`.
*
* .. warning::
*
* Application should properly limit the total buffer size to store
* incoming header fields. Without it, peer may send large number
* of header fields or large header fields to cause out of memory in
* local endpoint. Due to how HPACK works, peer can do this
* effectively without using much memory on their own.
*/ */
typedef int (*nghttp2_on_header_callback)(nghttp2_session *session, typedef int (*nghttp2_on_header_callback)(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,

View File

@ -447,6 +447,7 @@ Stream::Stream(Http2Handler *handler, int32_t stream_id)
file_ent(nullptr), file_ent(nullptr),
body_length(0), body_length(0),
body_offset(0), body_offset(0),
header_buffer_size(0),
stream_id(stream_id), stream_id(stream_id),
echo_upload(false) { echo_upload(false) {
auto config = handler->get_config(); auto config = handler->get_config();
@ -1389,6 +1390,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0; return 0;
} }
if (stream->header_buffer_size + namelen + valuelen > 64_k) {
hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
return 0;
}
stream->header_buffer_size += namelen + valuelen;
auto token = http2::lookup_token(name, namelen); auto token = http2::lookup_token(name, namelen);
http2::index_header(stream->hdidx, token, stream->headers.size()); http2::index_header(stream->hdidx, token, stream->headers.size());

View File

@ -119,6 +119,9 @@ struct Stream {
ev_timer wtimer; ev_timer wtimer;
int64_t body_length; int64_t body_length;
int64_t body_offset; int64_t body_offset;
// Total amount of bytes (sum of name and value length) used in
// headers.
size_t header_buffer_size;
int32_t stream_id; int32_t stream_id;
http2::HeaderIndex hdidx; http2::HeaderIndex hdidx;
bool echo_upload; bool echo_upload;

View File

@ -32,7 +32,7 @@ namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
namespace client { namespace client {
request_impl::request_impl() : strm_(nullptr) {} request_impl::request_impl() : strm_(nullptr), header_buffer_size_(0) {}
void request_impl::write_trailer(header_map h) { void request_impl::write_trailer(header_map h) {
auto sess = strm_->session(); auto sess = strm_->session();
@ -105,6 +105,12 @@ void request_impl::method(std::string s) { method_ = std::move(s); }
const std::string &request_impl::method() const { return method_; } const std::string &request_impl::method() const { return method_; }
size_t request_impl::header_buffer_size() const { return header_buffer_size_; }
void request_impl::update_header_buffer_size(size_t len) {
header_buffer_size_ += len;
}
} // namespace client } // namespace client
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -75,6 +75,9 @@ public:
void method(std::string s); void method(std::string s);
const std::string &method() const; const std::string &method() const;
size_t header_buffer_size() const;
void update_header_buffer_size(size_t len);
private: private:
header_map header_; header_map header_;
response_cb response_cb_; response_cb response_cb_;
@ -84,6 +87,7 @@ private:
class stream *strm_; class stream *strm_;
uri_ref uri_; uri_ref uri_;
std::string method_; std::string method_;
size_t header_buffer_size_;
}; };
} // namespace client } // namespace client

View File

@ -30,7 +30,8 @@ namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
namespace client { namespace client {
response_impl::response_impl() : content_length_(-1), status_code_(0) {} response_impl::response_impl()
: content_length_(-1), header_buffer_size_(0), status_code_(0) {}
void response_impl::on_data(data_cb cb) { data_cb_ = std::move(cb); } void response_impl::on_data(data_cb cb) { data_cb_ = std::move(cb); }
@ -52,6 +53,12 @@ header_map &response_impl::header() { return header_; }
const header_map &response_impl::header() const { return header_; } const header_map &response_impl::header() const { return header_; }
size_t response_impl::header_buffer_size() const { return header_buffer_size_; }
void response_impl::update_header_buffer_size(size_t len) {
header_buffer_size_ += len;
}
} // namespace client } // namespace client
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -53,12 +53,16 @@ public:
header_map &header(); header_map &header();
const header_map &header() const; const header_map &header() const;
size_t header_buffer_size() const;
void update_header_buffer_size(size_t len);
private: private:
data_cb data_cb_; data_cb data_cb_;
header_map header_; header_map header_;
int64_t content_length_; int64_t content_length_;
size_t header_buffer_size_;
int status_code_; int status_code_;
}; };

View File

@ -183,6 +183,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
if (token == http2::HD__STATUS) { if (token == http2::HD__STATUS) {
res.status_code(util::parse_uint(value, valuelen)); res.status_code(util::parse_uint(value, valuelen));
} else { } else {
if (res.header_buffer_size() + namelen + valuelen > 64_k) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
break;
}
res.update_header_buffer_size(namelen + valuelen);
if (token == http2::HD_CONTENT_LENGTH) { if (token == http2::HD_CONTENT_LENGTH) {
res.content_length(util::parse_uint(value, valuelen)); res.content_length(util::parse_uint(value, valuelen));
@ -223,6 +229,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
} }
// fall through // fall through
default: default:
if (req.header_buffer_size() + namelen + valuelen > 64_k) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
break;
}
req.update_header_buffer_size(namelen + valuelen);
req.header().emplace( req.header().emplace(
std::string(name, name + namelen), std::string(name, name + namelen),
header_value{std::string(value, value + valuelen), header_value{std::string(value, value + valuelen),

View File

@ -105,6 +105,13 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
} }
// fall through // fall through
default: default:
if (req.header_buffer_size() + namelen + valuelen > 64_k) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
break;
}
req.update_header_buffer_size(namelen + valuelen);
req.header().emplace(std::string(name, name + namelen), req.header().emplace(std::string(name, name + namelen),
header_value{std::string(value, value + valuelen), header_value{std::string(value, value + valuelen),
(flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0}); (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});

View File

@ -28,7 +28,7 @@ namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
namespace server { namespace server {
request_impl::request_impl() : strm_(nullptr) {} request_impl::request_impl() : strm_(nullptr), header_buffer_size_(0) {}
const header_map &request_impl::header() const { return header_; } const header_map &request_impl::header() const { return header_; }
@ -62,6 +62,12 @@ void request_impl::remote_endpoint(boost::asio::ip::tcp::endpoint ep) {
remote_ep_ = std::move(ep); remote_ep_ = std::move(ep);
} }
size_t request_impl::header_buffer_size() const { return header_buffer_size_; }
void request_impl::update_header_buffer_size(size_t len) {
header_buffer_size_ += len;
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -58,6 +58,9 @@ public:
const boost::asio::ip::tcp::endpoint &remote_endpoint() const; const boost::asio::ip::tcp::endpoint &remote_endpoint() const;
void remote_endpoint(boost::asio::ip::tcp::endpoint ep); void remote_endpoint(boost::asio::ip::tcp::endpoint ep);
size_t header_buffer_size() const;
void update_header_buffer_size(size_t len);
private: private:
class stream *strm_; class stream *strm_;
header_map header_; header_map header_;
@ -65,6 +68,7 @@ private:
uri_ref uri_; uri_ref uri_;
data_cb on_data_cb_; data_cb on_data_cb_;
boost::asio::ip::tcp::endpoint remote_ep_; boost::asio::ip::tcp::endpoint remote_ep_;
size_t header_buffer_size_;
}; };
} // namespace server } // namespace server

View File

@ -155,6 +155,7 @@ Request::Request(const std::string &uri, const http_parser_url &u,
inflater(nullptr), inflater(nullptr),
html_parser(nullptr), html_parser(nullptr),
data_prd(data_prd), data_prd(data_prd),
header_buffer_size(0),
stream_id(-1), stream_id(-1),
status(0), status(0),
level(level), level(level),
@ -1736,6 +1737,14 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
break; break;
} }
if (req->header_buffer_size + namelen + valuelen > 64_k) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
req->header_buffer_size += namelen + valuelen;
auto token = http2::lookup_token(name, namelen); auto token = http2::lookup_token(name, namelen);
http2::index_header(req->res_hdidx, token, req->res_nva.size()); http2::index_header(req->res_hdidx, token, req->res_nva.size());
@ -1751,6 +1760,15 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
break; break;
} }
if (req->header_buffer_size + namelen + valuelen > 64_k) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
req->header_buffer_size += namelen + valuelen;
auto token = http2::lookup_token(name, namelen); auto token = http2::lookup_token(name, namelen);
http2::index_header(req->req_hdidx, token, req->req_nva.size()); http2::index_header(req->req_hdidx, token, req->req_nva.size());
@ -1838,6 +1856,10 @@ int on_frame_recv_callback2(nghttp2_session *session,
if (!req) { if (!req) {
break; break;
} }
// Reset for response header field reception
req->header_buffer_size = 0;
auto scheme = req->get_req_header(http2::HD__SCHEME); auto scheme = req->get_req_header(http2::HD__SCHEME);
auto authority = req->get_req_header(http2::HD__AUTHORITY); auto authority = req->get_req_header(http2::HD__AUTHORITY);
auto path = req->get_req_header(http2::HD__PATH); auto path = req->get_req_header(http2::HD__PATH);

View File

@ -150,6 +150,7 @@ struct Request {
nghttp2_gzip *inflater; nghttp2_gzip *inflater;
HtmlParser *html_parser; HtmlParser *html_parser;
const nghttp2_data_provider *data_prd; const nghttp2_data_provider *data_prd;
size_t header_buffer_size;
int32_t stream_id; int32_t stream_id;
int status; int status;
// Recursion level: 0: first entity, 1: entity linked from first entity // Recursion level: 0: first entity, 1: entity linked from first entity