Skip to content

Commit

Permalink
[1.21] CVE-2021-43825
Browse files Browse the repository at this point in the history
Signed-off-by: Yan Avlasov <[email protected]>
  • Loading branch information
yanavlasov committed Feb 22, 2022
1 parent 8b05f8f commit 2728610
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 15 deletions.
6 changes: 3 additions & 3 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) {
// here is when Envoy "ends" the stream by calling recreateStream at which point recreateStream
// explicitly nulls out response_encoder to avoid the downstream being notified of the
// Envoy-internal stream instance being ended.
if (stream.response_encoder_ != nullptr &&
(!stream.filter_manager_.remoteComplete() || !stream.state_.codec_saw_local_complete_)) {
if (stream.response_encoder_ != nullptr && (!stream.filter_manager_.remoteDecodeComplete() ||
!stream.state_.codec_saw_local_complete_)) {
// Indicate local is complete at this point so that if we reset during a continuation, we don't
// raise further data or trailers.
ENVOY_STREAM_LOG(debug, "doEndStream() resetting stream", stream);
Expand Down Expand Up @@ -1421,7 +1421,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ResponseHeaderMap& heade
// If we are destroying a stream before remote is complete and the connection does not support
// multiplexing, we should disconnect since we don't want to wait around for the request to
// finish.
if (!filter_manager_.remoteComplete()) {
if (!filter_manager_.remoteDecodeComplete()) {
if (connection_manager_.codec_->protocol() < Protocol::Http2) {
connection_manager_.drain_state_ = DrainState::Closing;
}
Expand Down
15 changes: 12 additions & 3 deletions source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ bool ActiveStreamDecoderFilter::canContinue() {
return !parent_.state_.local_complete_;
}

bool ActiveStreamEncoderFilter::canContinue() {
// As with ActiveStreamDecoderFilter::canContinue() make sure we do not
// continue if a local reply has been sent.
return !parent_.state_.remote_encode_complete_;
}

Buffer::InstancePtr ActiveStreamDecoderFilter::createBuffer() {
auto buffer = dispatcher().getWatermarkFactory().createBuffer(
[this]() -> void { this->requestDataDrained(); },
Expand All @@ -316,7 +322,7 @@ Buffer::InstancePtr& ActiveStreamDecoderFilter::bufferedData() {
return parent_.buffered_request_data_;
}

bool ActiveStreamDecoderFilter::complete() { return parent_.state_.remote_complete_; }
bool ActiveStreamDecoderFilter::complete() { return parent_.state_.remote_decode_complete_; }

void ActiveStreamDecoderFilter::doHeaders(bool end_stream) {
parent_.decodeHeaders(this, *parent_.filter_manager_callbacks_.requestHeaders(), end_stream);
Expand Down Expand Up @@ -828,8 +834,8 @@ void FilterManager::decodeMetadata(ActiveStreamDecoderFilter* filter, MetadataMa
}

void FilterManager::maybeEndDecode(bool end_stream) {
ASSERT(!state_.remote_complete_);
state_.remote_complete_ = end_stream;
ASSERT(!state_.remote_decode_complete_);
state_.remote_decode_complete_ = end_stream;
if (end_stream) {
stream_info_.downstreamTiming().onLastDownstreamRxByteReceived(dispatcher().timeSource());
ENVOY_STREAM_LOG(debug, "request end stream", *this);
Expand Down Expand Up @@ -1352,6 +1358,8 @@ void FilterManager::encodeTrailers(ActiveStreamEncoderFilter* filter,

void FilterManager::maybeEndEncode(bool end_stream) {
if (end_stream) {
ASSERT(!state_.remote_encode_complete_);
state_.remote_encode_complete_ = true;
filter_manager_callbacks_.endStream();
}
}
Expand Down Expand Up @@ -1641,6 +1649,7 @@ Http1StreamEncoderOptionsOptRef ActiveStreamEncoderFilter::http1StreamEncoderOpt
}

void ActiveStreamEncoderFilter::responseDataTooLarge() {
ENVOY_STREAM_LOG(debug, "response data too large watermark exceeded", parent_);
if (parent_.state_.encoder_filters_streaming_) {
onEncoderFilterAboveWriteBufferHighWatermark();
} else {
Expand Down
18 changes: 9 additions & 9 deletions source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ struct ActiveStreamEncoderFilter : public ActiveStreamFilterBase,
: ActiveStreamFilterBase(parent, dual_filter, std::move(match_state)), handle_(filter) {}

// ActiveStreamFilterBase
bool canContinue() override { return true; }
bool canContinue() override;
Buffer::InstancePtr createBuffer() override;
Buffer::InstancePtr& bufferedData() override;
bool complete() override;
Expand Down Expand Up @@ -912,7 +912,7 @@ class FilterManager : public ScopeTrackedObject,
/**
* Whether remote processing has been marked as complete.
*/
bool remoteComplete() const { return state_.remote_complete_; }
bool remoteDecodeComplete() const { return state_.remote_decode_complete_; }

/**
* Instructs the FilterManager to not create a filter chain. This makes it possible to issue
Expand Down Expand Up @@ -1067,15 +1067,15 @@ class FilterManager : public ScopeTrackedObject,

struct State {
State()
: remote_complete_(false), local_complete_(false), has_1xx_headers_(false),
created_filter_chain_(false), is_head_request_(false), is_grpc_request_(false),
non_100_response_headers_encoded_(false), under_on_local_reply_(false),
decoder_filter_chain_aborted_(false), encoder_filter_chain_aborted_(false),
saw_downstream_reset_(false) {}

: remote_encode_complete_(false), remote_decode_complete_(false), local_complete_(false),
has_1xx_headers_(false), created_filter_chain_(false), is_head_request_(false),
is_grpc_request_(false), non_100_response_headers_encoded_(false),
under_on_local_reply_(false), decoder_filter_chain_aborted_(false),
encoder_filter_chain_aborted_(false), saw_downstream_reset_(false) {}
uint32_t filter_call_state_{0};

bool remote_complete_ : 1;
bool remote_encode_complete_ : 1;
bool remote_decode_complete_ : 1;
bool local_complete_ : 1; // This indicates that local is complete prior to filter processing.
// A filter can still stop the stream from being complete as seen
// by the codec.
Expand Down
1 change: 1 addition & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ envoy_cc_test_library(
"//source/common/http:header_map_lib",
"//source/extensions/filters/http/buffer:config",
"//test/common/http/http2:http2_frame",
"//test/integration/filters:buffer_continue_filter_lib",
"//test/integration/filters:continue_after_local_reply_filter_lib",
"//test/integration/filters:continue_headers_only_inject_body",
"//test/integration/filters:encoder_decoder_buffer_filter_lib",
Expand Down
14 changes: 14 additions & 0 deletions test/integration/filters/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,20 @@ envoy_cc_test_library(
],
)

envoy_cc_test_library(
name = "buffer_continue_filter_lib",
srcs = [
"buffer_continue_filter.cc",
],
deps = [
"//envoy/http:filter_interface",
"//envoy/registry",
"//envoy/server:filter_config_interface",
"//source/extensions/filters/http/common:pass_through_filter_lib",
"//test/extensions/filters/http/common:empty_http_filter_config_lib",
],
)

envoy_cc_test_library(
name = "test_socket_interface_lib",
srcs = [
Expand Down
74 changes: 74 additions & 0 deletions test/integration/filters/buffer_continue_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <string>

#include "envoy/http/filter.h"
#include "envoy/registry/registry.h"
#include "envoy/server/filter_config.h"

#include "source/extensions/filters/http/common/pass_through_filter.h"

#include "test/extensions/filters/http/common/empty_http_filter_config.h"

namespace Envoy {

// A filter that buffers until the limit is reached and then continues.
class BufferContinueStreamFilter : public Http::PassThroughFilter {
public:
Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool end_stream) override {
return end_stream ? Http::FilterHeadersStatus::Continue
: Http::FilterHeadersStatus::StopIteration;
}

Http::FilterDataStatus decodeData(Buffer::Instance&, bool end_stream) override {
return end_stream ? Http::FilterDataStatus::Continue
: Http::FilterDataStatus::StopIterationAndBuffer;
}

Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, bool) override {
response_headers_ = &headers;
return Http::FilterHeadersStatus::StopIteration;
}

Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override {
data_total_ += data.length();

const auto limit = encoder_callbacks_->encoderBufferLimit();
const auto header_size = response_headers_->byteSize();

if (limit && header_size + data_total_ > limit) {
// Give up since we've reached the buffer limit, Envoy should generate
// a 500 since it couldn't finished encoding.
return Http::FilterDataStatus::Continue;
}

encoder_callbacks_->addEncodedData(data, false);

if (!end_stream) {
return Http::FilterDataStatus::StopIterationAndBuffer;
}

return Http::FilterDataStatus::Continue;
}

private:
Http::ResponseHeaderMap* response_headers_;
uint64_t data_total_{0};
};

class BufferContinueFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig {
public:
BufferContinueFilterConfig() : EmptyHttpFilterConfig("buffer-continue-filter") {}

Http::FilterFactoryCb createFilter(const std::string&,
Server::Configuration::FactoryContext&) override {
return [](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(std::make_shared<::Envoy::BufferContinueStreamFilter>());
};
}
};

// perform static registration
static Registry::RegisterFactory<BufferContinueFilterConfig,
Server::Configuration::NamedHttpFilterConfigFactory>
register_;

} // namespace Envoy
52 changes: 52 additions & 0 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3485,6 +3485,58 @@ TEST_P(ProtocolIntegrationTest, FragmentStrippedFromPathWithOverride) {
EXPECT_EQ("200", response->headers().getStatusValue());
}

// Test buffering and then continuing after too many response bytes to buffer.
TEST_P(ProtocolIntegrationTest, BufferContinue) {
// Bytes sent is configured for http/2 flow control windows.
if (upstreamProtocol() != Http::CodecType::HTTP2) {
return;
}
config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) -> void {
auto* route_config = hcm.mutable_route_config();
auto* virtual_host = route_config->mutable_virtual_hosts(0);
auto* header = virtual_host->mutable_response_headers_to_add()->Add()->mutable_header();
header->set_key("foo");
header->set_value("bar");
});

useAccessLog();
config_helper_.addFilter("{ name: buffer-continue-filter, typed_config: { \"@type\": "
"type.googleapis.com/google.protobuf.Empty } }");
config_helper_.setBufferLimits(1024, 1024);
initialize();

// Send the request.
codec_client_ = makeHttpConnection(lookupPort("http"));
auto encoder_decoder = codec_client_->startRequest(default_request_headers_);
auto downstream_request = &encoder_decoder.first;
auto response = std::move(encoder_decoder.second);
Buffer::OwnedImpl data("HTTP body content goes here");
codec_client_->sendData(*downstream_request, data, true);
waitForNextUpstreamRequest();

// Send the response headers.
upstream_request_->encodeHeaders(default_response_headers_, false);

// Now send an overly large response body. At some point, too much data will
// be buffered, the stream will be reset, and the connection will disconnect.
upstream_request_->encodeData(512, false);
upstream_request_->encodeData(1024 * 100, false);

if (upstreamProtocol() == Http::CodecType::HTTP1) {
ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect());
} else {
ASSERT_TRUE(upstream_request_->waitForReset());
ASSERT_TRUE(fake_upstream_connection_->close());
ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect());
}

ASSERT_TRUE(response->waitForEndStream());
EXPECT_TRUE(response->complete());
EXPECT_EQ("500", response->headers().getStatusValue());
}

TEST_P(DownstreamProtocolIntegrationTest, ContentLengthSmallerThanPayload) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
Expand Down

0 comments on commit 2728610

Please sign in to comment.