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 2 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
6 changes: 3 additions & 3 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,9 @@ 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",
urls = ["https://github.com/nodejs/http-parser/archive/4f15b7d510dc7c6361a26a7c6d2f7c3a17f8d878.tar.gz"],
use_category = ["dataplane"],
cpe = "cpe:2.3:a:nodejs:node.js:*",
),
Expand Down
4 changes: 4 additions & 0 deletions include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ struct Http1Settings {
// - Is neither a HEAD only request nor a HTTP Upgrade
// - Not a HEAD request
bool enable_trailers_{false};
// Allow serving requests with both Content-Length and Transfer-Encoding: chunked.
veshij marked this conversation as resolved.
Show resolved Hide resolved
// In enabled and such request served - Content-Length is ignored and removed from request
veshij marked this conversation as resolved.
Show resolved Hide resolved
// headers.
bool allow_chunked_length{false};

enum class HeaderKeyFormat {
// By default no formatting is performed, presenting all headers in lowercase (as Envoy
Expand Down
26 changes: 25 additions & 1 deletion source/common/http/http1/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,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 @@ -878,6 +879,30 @@ int ServerConnectionImpl::onHeadersComplete() {
"http/1.1 protocol error: request headers failed spec compliance checks");
}

// 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 downstream.
veshij marked this conversation as resolved.
Show resolved Hide resolved

// Reject request with Http::Code::BadRequest by default or remove Content-Length header
// and serve request if allowed by http1 codec settings.
if (parser_.uses_transfer_encoding != 0 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry why this uses_transfer_encoding check? Couldn't you just do (parser_.flags & F_CHUNKED)? Or you are checking for any transfer encoding? Can you add more comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following RFC here (any TE, not only chunked):

If a message is received with both a Transfer-Encoding and a
Content-Length header field, the Transfer-Encoding overrides the
Content-Length.

(parser_.content_length > 0 && parser_.content_length != ULLONG_MAX)) {
veshij marked this conversation as resolved.
Show resolved Hide resolved
if ((parser_.flags & F_CHUNKED) && allowChunkedLength()) {
headers->removeContentLength();
} else {
ENVOY_CONN_LOG(error, "Both 'Content-Length' and 'Transfer-Encdoding' are set.",
veshij marked this conversation as resolved.
Show resolved Hide resolved
connection_);
error_code_ = Http::Code::BadRequest;
sendProtocolError(Http1ResponseCodeDetails::get().ContentLengthNotAllowed);
throw CodecProtocolException("Both Content-Length and Transfer-Encdoding headers are set.");
}
}

// Determine here whether we have a body or not. This uses the new RFC semantics where the
// presence of content-length or chunked transfer-encoding indicates a body vs. a particular
// method. If there is no body, we defer raising decodeHeaders() until the parser is flushed
Expand All @@ -887,7 +912,6 @@ int ServerConnectionImpl::onHeadersComplete() {
if (parser_.flags & F_CHUNKED ||
(parser_.content_length > 0 && parser_.content_length != ULLONG_MAX) || handling_upgrade_) {
active_request.request_decoder_->decodeHeaders(std::move(headers), false);

// If the connection has been closed (or is closing) after decoding headers, pause the parser
// so we return control to the caller.
if (connection_.state() != Network::Connection::State::Open) {
Expand Down
2 changes: 2 additions & 0 deletions source/common/http/http1/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
}
uint32_t bufferLimit() { return connection_.bufferLimit(); }
virtual bool supportsHttp10() { return false; }
virtual bool allowChunkedLength() { return false; }
veshij marked this conversation as resolved.
Show resolved Hide resolved
bool maybeDirectDispatch(Buffer::Instance& data);
virtual void maybeAddSentinelBufferFragment(Buffer::WatermarkBuffer&) {}
CodecStats& stats() { return stats_; }
Expand Down Expand Up @@ -426,6 +427,7 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl {
envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction
headers_with_underscores_action);
bool supportsHttp10() override { return codec_settings_.accept_http_10_; }
bool allowChunkedLength() override { return codec_settings_.allow_chunked_length; }

protected:
/**
Expand Down
3 changes: 2 additions & 1 deletion test/integration/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ TEST_P(IntegrationTest, TestSmuggling) {
"identity,chunked \r\ncontent-length: 36\r\n\r\n" +
smuggled_request;
sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response, false);
EXPECT_THAT(response, HasSubstr("HTTP/1.1 400 Bad Request\r\n"));
// 'identity' encoding is not supported
EXPECT_THAT(response, HasSubstr("HTTP/1.1 501 Not Implemented\r\n"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems not right. Why are we now returning a 501 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onHeadersCompleteBase checks for unsupported encodings (identity in this case) and returns 501 if unsupported encoding is detected.
if everything is ok - passes control to onHeadersComplete() where my code is executed.

I can refactor it to do CL/TE checks before TE encoding check which returns 501, I'm not sure which order is correct in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nginx's response in case of unsupported encoding and CL set:

> POST / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.64.1
> Accept: */*
> Transfer-Encoding: unsupported
> Content-Length: 10
>
< HTTP/1.1 501 Not Implemented
< Server: nginx
< Date: Fri, 31 Jul 2020 20:40:47 GMT
< Content-Type: text/html
< Content-Length: 14480
< Connection: close

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naively it seems like we should return a 4xx code here, but I'm not an expert. If this is what NGINX does that seems fine, thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I see by allowing this in the parser we get slightly different behavior here. I think this is OK but I think we should release note this also and I would also like to hear from @antoniovicente or @PiotrSikora on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Friendly ping on the release note on the subtle behavior change here.

/wait

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4xx makes more sense to me in this case.

This specific change seems unrelated to the rest of the functional changes in this PR, I would suggest doing the http_parser version bump separately from the rest of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can split this PR in two, one for library update and one for functional change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific change seems unrelated to the rest of the functional changes in this PR, I would suggest doing the http_parser version bump separately from the rest of this PR.

@veshij correct me if I'm wrong, but I don't think this has to do with the library update. It has to do with the fact that we are now hitting code for this case that we did not hit before, and Envoy is returning 501 when it used to return 400. Right? If so I think this seems OK to me with a release note but will defer to @antoniovicente and @PiotrSikora on that.

Copy link
Contributor Author

@veshij veshij Aug 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest doing the http_parser version bump separately

@mattklein123 Sorry, that was a reply to comment ^

}
}

Expand Down