Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[http1 codec] Allow requests with Transfer-Encoding and Content-Length headers set. #12349

Merged
merged 46 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
886c5f0
[http1 codec] remove Content-Length is Transfer-Encoding is chunked
veshij Jul 26, 2020
6572594
enable http parser option by default and do all validation in Envoy
veshij Jul 29, 2020
8cc229c
address comments: typo/spelling/comments
veshij Jul 29, 2020
542e6e3
add allow_chunked_length to Http1ProtocolOoptions
veshij Jul 29, 2020
b59bcea
add allow_chunked_length to Http1ProtocolOoptions
veshij Jul 29, 2020
ac88d96
Merge branch 'chunked' of github.com:veshij/envoy into chunked
veshij Jul 30, 2020
7c91d03
add unit tests
veshij Jul 30, 2020
0cd4cb7
typo fix
veshij Jul 30, 2020
4a88b62
release notes update
veshij Jul 30, 2020
4119c78
typo fix
veshij Jul 30, 2020
e1f3801
Merge remote-tracking branch 'upstream/master' into chunked
veshij Jul 31, 2020
f4776d6
fix version_histroy after merge
veshij Jul 31, 2020
3870fda
Merge remote-tracking branch 'upstream/master' into chunked
veshij Jul 31, 2020
db56f66
typo fix & proto gen
veshij Jul 31, 2020
e9f2f1f
sendLocalReply signature was changed, update test
veshij Jul 31, 2020
c7424f0
implementation for legacy codec, update tests
veshij Jul 31, 2020
9815afb
address comments
veshij Jul 31, 2020
e6c38f5
update comment in proto
veshij Jul 31, 2020
004dc2a
use configuration option directly
veshij Jul 31, 2020
de444cc
fix typo
veshij Jul 31, 2020
af435ca
cleanup legacy codec
veshij Aug 1, 2020
9efa4e2
blankline
veshij Aug 1, 2020
9bbc964
check for Content-Length header, not for parser_.content_length
veshij Aug 5, 2020
43e18a1
add testcases for different content-length value
veshij Aug 6, 2020
09b9b11
add minor behavior change note
veshij Aug 6, 2020
1fbb0f8
update proto comment
veshij Aug 6, 2020
02a98d4
add ClientConnectionImpl checks
veshij Aug 6, 2020
57a8b1f
move settings to ConnectionImpl
veshij Aug 6, 2020
5f13f37
update field description with requests/responses
veshij Aug 6, 2020
b082ee4
fix typo in Transfer-Encdoding
veshij Aug 6, 2020
4102c14
move validation logic to ConnectionImpl::onHeadersCompleteBase() to a…
veshij Aug 7, 2020
467e7d0
change check order
veshij Aug 7, 2020
984b394
change field order in ConnectionImpl
veshij Aug 11, 2020
ff024e7
add response test
veshij Aug 13, 2020
ac60a70
implement response tests, fix response codec for content-length=0 case
veshij Aug 13, 2020
cfcfc35
Merge remote-tracking branch 'upstream/master' into chunked
veshij Aug 13, 2020
97e7828
use testingNewCodec() instead of GetParam()
veshij Aug 13, 2020
536f724
define Http1ResponseCodeDetails::get().ChunkedContentLength and updat…
veshij Aug 24, 2020
ee525a0
Merge remote-tracking branch 'upstream/master' into chunked
veshij Aug 25, 2020
ac1d0e1
simple integration test
veshij Aug 26, 2020
2549c62
typo fix
veshij Aug 26, 2020
62ef94a
server/client integration tests
veshij Aug 26, 2020
85864ec
format fix
veshij Aug 26, 2020
bd9d235
Kick CI
veshij Aug 26, 2020
c959d58
Kick CI
veshij Aug 26, 2020
8cf9af0
Merge remote-tracking branch 'upstream/master' into chunked
veshij Aug 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ message HttpProtocolOptions {
HeadersWithUnderscoresAction headers_with_underscores_action = 5;
}

// [#next-free-field: 6]
// [#next-free-field: 7]
message Http1ProtocolOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http1ProtocolOptions";
Expand Down Expand Up @@ -157,6 +157,16 @@ message Http1ProtocolOptions {
// - Not a response to a HEAD request.
// - The content length header is not present.
bool enable_trailers = 5;

// Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding`
// headers set. By default such messages are rejected, but if option is enabled - Envoy will
// remove Content-Length header and process message.
// See `RFC7230, sec. 3.3.3 <https://tools.ietf.org/html/rfc7230#section-3.3.3>` for details.
//
// .. attention::
// Enabling this option might lead to request smuggling vulnerability, especially if traffic
// is proxied via multiple layers of proxies.
bool allow_chunked_length = 6;
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
}

// [#next-free-field: 15]
Expand Down
12 changes: 11 additions & 1 deletion api/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,12 @@ DEPENDENCY_REPOSITORIES = dict(
cpe = "N/A",
),
com_github_nodejs_http_parser = dict(
sha256 = "8fa0ab8770fd8425a9b431fdbf91623c4d7a9cdb842b9339289bd2b0b01b0d3d",
strip_prefix = "http-parser-2.9.3",
urls = ["https://github.com/nodejs/http-parser/archive/v2.9.3.tar.gz"],
sha256 = "6a12896313ce1ca630cf516a0ee43a79b5f13f5a5d8143f56560ac0b21c98fac",
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
strip_prefix = "http-parser-4f15b7d510dc7c6361a26a7c6d2f7c3a17f8d878",
# 2020-07-10
# This SHA includes fix for https://github.com/nodejs/http-parser/issues/517 which allows (opt-in) to serve
# requests with both Content-Legth and Transfer-Encoding: chunked headers set.
urls = ["https://github.com/nodejs/http-parser/archive/4f15b7d510dc7c6361a26a7c6d2f7c3a17f8d878.tar.gz"],
use_category = ["dataplane"],
cpe = "cpe:2.3:a:nodejs:node.js:*",
),
Expand Down
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ New Features
* ext_authz filter: added support for emitting dynamic metadata for both :ref:`HTTP <config_http_filters_ext_authz_dynamic_metadata>` and :ref:`network <config_network_filters_ext_authz_dynamic_metadata>` filters.
* grpc-json: support specifying `response_body` field in for `google.api.HttpBody` message.
* http: added support for :ref:`%DOWNSTREAM_PEER_FINGERPRINT_1% <config_http_conn_man_headers_custom_request_headers>` as custom header.
* http: added :ref:`allow_chunked_length <envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.allow_chunked_length>` configuration option for HTTP/1 codec to allow processing requests/responses with both Content-Length and Transfer-Encoding: chunked headers. If such message is served and option is enabled - per RFC Content-Length is ignored and removed.
* http: introduced new HTTP/1 and HTTP/2 codec implementations that will remove the use of exceptions for control flow due to high risk factors and instead use error statuses. The old behavior is used by default, but the new codecs can be enabled for testing by setting the runtime feature `envoy.reloadable_features.new_codec_behavior` to true. The new codecs will be in development for one month, and then enabled by default while the old codecs are deprecated.
* load balancer: added a :ref:`configuration<envoy_v3_api_msg_config.cluster.v3.Cluster.LeastRequestLbConfig>` option to specify the active request bias used by the least request load balancer.
* lua: added Lua APIs to access :ref:`SSL connection info <config_http_filters_lua_ssl_socket_info>` object.
Expand Down
12 changes: 11 additions & 1 deletion generated_api_shadow/envoy/config/core/v3/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion generated_api_shadow/envoy/config/core/v4alpha/protocol.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ struct Http1Settings {
// - Is neither a HEAD only request nor a HTTP Upgrade
// - Not a HEAD request
bool enable_trailers_{false};
// Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding`
// headers set. By default such messages are rejected, but if option is enabled - Envoy will
// remove Content-Length header and process message.
bool allow_chunked_length_{false};

enum class HeaderKeyFormat {
// By default no formatting is performed, presenting all headers in lowercase (as Envoy
Expand Down
47 changes: 35 additions & 12 deletions source/common/http/http1/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -442,15 +442,14 @@ http_parser_settings ConnectionImpl::settings_{
};

ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stats,
http_parser_type type, uint32_t max_headers_kb,
const uint32_t max_headers_count,
HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers)
: connection_(connection), stats_(stats),
const Http1Settings& settings, http_parser_type type,
uint32_t max_headers_kb, const uint32_t max_headers_count,
HeaderKeyFormatterPtr&& header_key_formatter)
: connection_(connection), stats_(stats), codec_settings_(settings),
header_key_formatter_(std::move(header_key_formatter)), processing_trailers_(false),
handling_upgrade_(false), reset_stream_called_(false), deferred_end_stream_headers_(false),
connection_header_sanitization_(Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.connection_header_sanitization")),
enable_trailers_(enable_trailers),
strict_1xx_and_204_headers_(Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.strict_1xx_and_204_response_headers")),
output_buffer_([&]() -> void { this->onBelowLowWatermark(); },
Expand All @@ -459,6 +458,7 @@ ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stat
max_headers_kb_(max_headers_kb), max_headers_count_(max_headers_count) {
output_buffer_.setWatermarks(connection.bufferLimit());
http_parser_init(&parser_, type);
parser_.allow_chunked_length = 1;
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
parser_.data = this;
}

Expand Down Expand Up @@ -580,7 +580,7 @@ void ConnectionImpl::onHeaderField(const char* data, size_t length) {
// We previously already finished up the headers, these headers are
// now trailers.
if (header_parsing_state_ == HeaderParsingState::Done) {
if (!enable_trailers_) {
if (!enableTrailers()) {
// Ignore trailers.
return;
}
Expand All @@ -598,7 +598,7 @@ void ConnectionImpl::onHeaderField(const char* data, size_t length) {
}

void ConnectionImpl::onHeaderValue(const char* data, size_t length) {
if (header_parsing_state_ == HeaderParsingState::Done && !enable_trailers_) {
if (header_parsing_state_ == HeaderParsingState::Done && !enableTrailers()) {
// Ignore trailers.
return;
}
Expand Down Expand Up @@ -674,6 +674,29 @@ int ConnectionImpl::onHeadersCompleteBase() {
handling_upgrade_ = true;
}

// https://tools.ietf.org/html/rfc7230#section-3.3.3
// If a message is received with both a Transfer-Encoding and a
// Content-Length header field, the Transfer-Encoding overrides the
// Content-Length. Such a message might indicate an attempt to
// perform request smuggling (Section 9.5) or response splitting
// (Section 9.4) and ought to be handled as an error. A sender MUST
// remove the received Content-Length field prior to forwarding such
// a message.

// Reject message with Http::Code::BadRequest if both Transfer-Encoding and Content-Length
// headers are present or if allowed by http1 codec settings and 'Transfer-Encoding'
// is chunked - remove Content-Length and serve request.
if (parser_.uses_transfer_encoding != 0 && request_or_response_headers.ContentLength()) {
if ((parser_.flags & F_CHUNKED) && codec_settings_.allow_chunked_length_) {
request_or_response_headers.removeContentLength();
} else {
error_code_ = Http::Code::BadRequest;
mattklein123 marked this conversation as resolved.
Show resolved Hide resolved
sendProtocolError(Http1ResponseCodeDetails::get().ContentLengthNotAllowed);
throw CodecProtocolException(
"http/1.1 protocol error: both 'Content-Length' and 'Transfer-Encoding' are set.");
veshij marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Per https://tools.ietf.org/html/rfc7230#section-3.3.1 Envoy should reject
// transfer-codings it does not understand.
// Per https://tools.ietf.org/html/rfc7231#section-4.3.6 a payload with a
Expand Down Expand Up @@ -762,9 +785,9 @@ ServerConnectionImpl::ServerConnectionImpl(
const uint32_t max_request_headers_count,
envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction
headers_with_underscores_action)
: ConnectionImpl(connection, stats, HTTP_REQUEST, max_request_headers_kb,
max_request_headers_count, formatter(settings), settings.enable_trailers_),
callbacks_(callbacks), codec_settings_(settings),
: ConnectionImpl(connection, stats, settings, HTTP_REQUEST, max_request_headers_kb,
max_request_headers_count, formatter(settings)),
callbacks_(callbacks),
response_buffer_releasor_([this](const Buffer::OwnedBufferFragmentImpl* fragment) {
releaseOutboundResponse(fragment);
}),
Expand Down Expand Up @@ -1055,8 +1078,8 @@ void ServerConnectionImpl::checkHeaderNameForUnderscores() {
ClientConnectionImpl::ClientConnectionImpl(Network::Connection& connection, CodecStats& stats,
ConnectionCallbacks&, const Http1Settings& settings,
const uint32_t max_response_headers_count)
: ConnectionImpl(connection, stats, HTTP_RESPONSE, MAX_RESPONSE_HEADERS_KB,
max_response_headers_count, formatter(settings), settings.enable_trailers_) {}
: ConnectionImpl(connection, stats, settings, HTTP_RESPONSE, MAX_RESPONSE_HEADERS_KB,
max_response_headers_count, formatter(settings)) {}

bool ClientConnectionImpl::cannotHaveBody() {
if (pending_response_.has_value() && pending_response_.value().encoder_.headRequest()) {
Expand Down
12 changes: 5 additions & 7 deletions source/common/http/http1/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
bool maybeDirectDispatch(Buffer::Instance& data);
virtual void maybeAddSentinelBufferFragment(Buffer::WatermarkBuffer&) {}
CodecStats& stats() { return stats_; }
bool enableTrailers() const { return enable_trailers_; }
bool enableTrailers() const { return codec_settings_.enable_trailers_; }

// Http::Connection
Http::Status dispatch(Buffer::Instance& data) override;
Expand All @@ -218,9 +218,9 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
bool strict1xxAnd204Headers() { return strict_1xx_and_204_headers_; }

protected:
ConnectionImpl(Network::Connection& connection, CodecStats& stats, http_parser_type type,
uint32_t max_headers_kb, const uint32_t max_headers_count,
HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers);
ConnectionImpl(Network::Connection& connection, CodecStats& stats, const Http1Settings& settings,
http_parser_type type, uint32_t max_headers_kb, const uint32_t max_headers_count,
HeaderKeyFormatterPtr&& header_key_formatter);

bool resetStreamCalled() { return reset_stream_called_; }
void onMessageBeginBase();
Expand All @@ -241,6 +241,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log

Network::Connection& connection_;
CodecStats& stats_;
const Http1Settings codec_settings_;
veshij marked this conversation as resolved.
Show resolved Hide resolved
http_parser parser_;
Http::Code error_code_{Http::Code::BadRequest};
const HeaderKeyFormatterPtr header_key_formatter_;
Expand All @@ -254,7 +255,6 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
// block with end stream set to true with no further protocol data remaining.
bool deferred_end_stream_headers_ : 1;
const bool connection_header_sanitization_ : 1;
const bool enable_trailers_ : 1;
const bool strict_1xx_and_204_headers_ : 1;

private:
Expand Down Expand Up @@ -500,7 +500,6 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl {

ServerConnectionCallbacks& callbacks_;
absl::optional<ActiveRequest> active_request_;
Http1Settings codec_settings_;
const Buffer::OwnedBufferFragmentImpl::Releasor response_buffer_releasor_;
uint32_t outbound_responses_{};
// This defaults to 2, which functionally disables pipelining. If any users
Expand All @@ -527,7 +526,6 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl {
ClientConnectionImpl(Network::Connection& connection, CodecStats& stats,
ConnectionCallbacks& callbacks, const Http1Settings& settings,
const uint32_t max_response_headers_count);

// Http::ClientConnection
RequestEncoder& newStream(ResponseDecoder& response_decoder) override;

Expand Down
Loading