nghttpx: Validate received response body length against content-length
This commit is contained in:
parent
f92110c54c
commit
441f1cc282
|
@ -109,9 +109,10 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
|
|||
: request_buf_(upstream ? upstream->get_mcpool() : nullptr),
|
||||
response_buf_(upstream ? upstream->get_mcpool() : nullptr),
|
||||
request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0),
|
||||
upstream_(upstream), request_headers_sum_(0), response_headers_sum_(0),
|
||||
request_datalen_(0), response_datalen_(0), stream_id_(stream_id),
|
||||
priority_(priority), downstream_stream_id_(-1),
|
||||
response_content_length_(-1), upstream_(upstream),
|
||||
request_headers_sum_(0), response_headers_sum_(0), request_datalen_(0),
|
||||
response_datalen_(0), stream_id_(stream_id), priority_(priority),
|
||||
downstream_stream_id_(-1),
|
||||
response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
|
||||
request_state_(INITIAL), request_major_(1), request_minor_(1),
|
||||
response_state_(INITIAL), response_http_status_(0), response_major_(1),
|
||||
|
@ -663,6 +664,24 @@ int64_t Downstream::get_response_sent_bodylen() const {
|
|||
return response_sent_bodylen_;
|
||||
}
|
||||
|
||||
void Downstream::set_response_content_length(int64_t len) {
|
||||
response_content_length_ = len;
|
||||
}
|
||||
|
||||
bool Downstream::validate_response_bodylen() const {
|
||||
if (!expect_response_body() || response_content_length_ == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, this) << "response invalid bodylen: content-length="
|
||||
<< response_content_length_
|
||||
<< ", received=" << response_bodylen_;
|
||||
}
|
||||
|
||||
return response_content_length_ == response_bodylen_;
|
||||
}
|
||||
|
||||
void Downstream::set_priority(int32_t pri) { priority_ = pri; }
|
||||
|
||||
int32_t Downstream::get_priority() const { return priority_; }
|
||||
|
@ -719,6 +738,14 @@ void Downstream::inspect_http1_response() {
|
|||
util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
|
||||
chunked_response_ = true;
|
||||
}
|
||||
|
||||
idx = response_hdidx_[http2::HD_CONTENT_LENGTH];
|
||||
if (idx != -1) {
|
||||
auto len = util::parse_uint(response_headers_[idx].value);
|
||||
if (len != -1) {
|
||||
response_content_length_ = len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Downstream::reset_response() {
|
||||
|
|
|
@ -218,6 +218,10 @@ public:
|
|||
int64_t get_response_bodylen() const;
|
||||
void add_response_sent_bodylen(size_t amount);
|
||||
int64_t get_response_sent_bodylen() const;
|
||||
void set_response_content_length(int64_t len);
|
||||
// Validates that received response body length and content-length
|
||||
// matches.
|
||||
bool validate_response_bodylen() const;
|
||||
uint32_t get_response_rst_stream_error_code() const;
|
||||
void set_response_rst_stream_error_code(uint32_t error_code);
|
||||
// Inspects HTTP/1 response. This checks tranfer-encoding etc.
|
||||
|
@ -297,13 +301,17 @@ private:
|
|||
ev_timer downstream_rtimer_;
|
||||
ev_timer downstream_wtimer_;
|
||||
|
||||
// the length of request body
|
||||
// the length of request body received so far
|
||||
int64_t request_bodylen_;
|
||||
// the length of response body
|
||||
// the length of response body received so far
|
||||
int64_t response_bodylen_;
|
||||
|
||||
// the length of response body sent to upstream client
|
||||
int64_t response_sent_bodylen_;
|
||||
|
||||
// content-length of response body, -1 if it is unknown.
|
||||
int64_t response_content_length_;
|
||||
|
||||
Upstream *upstream_;
|
||||
std::unique_ptr<DownstreamConnection> dconn_;
|
||||
|
||||
|
|
|
@ -817,23 +817,24 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto content_length =
|
||||
auto content_length_hd =
|
||||
downstream->get_response_header(http2::HD_CONTENT_LENGTH);
|
||||
if (!content_length && downstream->get_request_method() != "HEAD" &&
|
||||
downstream->get_request_method() != "CONNECT") {
|
||||
unsigned int status;
|
||||
status = downstream->get_response_http_status();
|
||||
if (!((100 <= status && status <= 199) || status == 204 || status == 304)) {
|
||||
// Here we have response body but Content-Length is not known
|
||||
// in advance.
|
||||
|
||||
if (content_length_hd) {
|
||||
auto content_length = util::parse_uint(content_length_hd->value);
|
||||
if (content_length != -1) {
|
||||
downstream->set_response_content_length(content_length);
|
||||
} else if (downstream->expect_response_body()) {
|
||||
// Here we have response body but Content-Length is not known in
|
||||
// advance.
|
||||
if (downstream->get_request_major() <= 0 ||
|
||||
downstream->get_request_minor() <= 0) {
|
||||
// We simply close connection for pre-HTTP/1.1 in this case.
|
||||
downstream->set_response_connection_close(true);
|
||||
} else {
|
||||
// Otherwise, use chunked encoding to keep upstream
|
||||
// connection open. In HTTP2, we are supporsed not to
|
||||
// receive transfer-encoding.
|
||||
} else if (downstream->get_request_method() != "CONNECT") {
|
||||
// Otherwise, use chunked encoding to keep upstream connection
|
||||
// open. In HTTP2, we are supporsed not to receive
|
||||
// transfer-encoding.
|
||||
downstream->add_response_header("transfer-encoding", "chunked");
|
||||
downstream->set_chunked_response(true);
|
||||
}
|
||||
|
|
|
@ -526,7 +526,9 @@ int on_frame_not_send_callback(nghttp2_session *session,
|
|||
<< ", lib_error_code=" << lib_error_code << ":"
|
||||
<< nghttp2_strerror(lib_error_code);
|
||||
if (frame->hd.type == NGHTTP2_HEADERS &&
|
||||
frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
|
||||
frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
|
||||
lib_error_code != NGHTTP2_ERR_STREAM_CLOSED &&
|
||||
lib_error_code != NGHTTP2_ERR_STREAM_CLOSING) {
|
||||
// To avoid stream hanging around, issue RST_STREAM.
|
||||
auto downstream = upstream->find_downstream(frame->hd.stream_id);
|
||||
if (downstream) {
|
||||
|
@ -1207,6 +1209,13 @@ int Http2Upstream::on_downstream_body_complete(Downstream *downstream) {
|
|||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream) << "HTTP response completed";
|
||||
}
|
||||
|
||||
if (!downstream->validate_response_bodylen()) {
|
||||
rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||
downstream->set_response_connection_close(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
||||
downstream->ensure_upstream_wtimer();
|
||||
|
||||
|
|
|
@ -768,6 +768,10 @@ int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
|||
DLOG(INFO, downstream) << "HTTP response completed";
|
||||
}
|
||||
|
||||
if (!downstream->validate_response_bodylen()) {
|
||||
downstream->set_response_connection_close(true);
|
||||
}
|
||||
|
||||
if (downstream->get_request_connection_close() ||
|
||||
downstream->get_response_connection_close()) {
|
||||
auto handler = get_client_handler();
|
||||
|
|
|
@ -336,7 +336,8 @@ void on_ctrl_not_send_callback(spdylay_session *session,
|
|||
ULOG(WARN, upstream) << "Failed to send control frame type=" << type
|
||||
<< ", error_code=" << error_code << ":"
|
||||
<< spdylay_strerror(error_code);
|
||||
if (type == SPDYLAY_SYN_REPLY) {
|
||||
if (type == SPDYLAY_SYN_REPLY && error_code != SPDYLAY_ERR_STREAM_CLOSED &&
|
||||
error_code != SPDYLAY_ERR_STREAM_CLOSING) {
|
||||
// To avoid stream hanging around, issue RST_STREAM.
|
||||
auto stream_id = frame->syn_reply.stream_id;
|
||||
auto downstream = upstream->find_downstream(stream_id);
|
||||
|
@ -917,6 +918,12 @@ int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) {
|
|||
DLOG(INFO, downstream) << "HTTP response completed";
|
||||
}
|
||||
|
||||
if (!downstream->validate_response_bodylen()) {
|
||||
rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
downstream->set_response_connection_close(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spdylay_session_resume_data(session_, downstream->get_stream_id());
|
||||
downstream->ensure_upstream_wtimer();
|
||||
|
||||
|
|
|
@ -954,6 +954,10 @@ int64_t parse_uint(const char *s) {
|
|||
return parse_uint(reinterpret_cast<const uint8_t *>(s), strlen(s));
|
||||
}
|
||||
|
||||
int64_t parse_uint(const std::string &s) {
|
||||
return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size());
|
||||
}
|
||||
|
||||
int64_t parse_uint(const uint8_t *s, size_t len) {
|
||||
if (len == 0) {
|
||||
return -1;
|
||||
|
|
|
@ -506,6 +506,7 @@ int64_t parse_uint_with_unit(const char *s);
|
|||
// the parsed integer. If there is an error, returns -1.
|
||||
int64_t parse_uint(const char *s);
|
||||
int64_t parse_uint(const uint8_t *s, size_t len);
|
||||
int64_t parse_uint(const std::string &s);
|
||||
|
||||
} // namespace util
|
||||
|
||||
|
|
Loading…
Reference in New Issue