Skip to content

Commit 9b314db

Browse files
agrawrohphlax
authored andcommitted
http: ensure decode* methods are blocked after a downstream reset
[CVE-2026-26311](GHSA-84xm-r438-86px) Signed-off-by: Rohit Agrawal <rohit.agrawal@databricks.com> Signed-off-by: Ryan Northey <ryan@synca.io>
1 parent 515ce0d commit 9b314db

4 files changed

Lines changed: 207 additions & 1 deletion

File tree

changelogs/current.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ bug_fixes:
3939
change: |
4040
Fixed a crash on listener removal with a process-level access log rate limiter
4141
:ref:`ProcessRateLimitFilter <envoy_v3_api_msg_extensions.access_loggers.filters.process_ratelimit.v3.ProcessRateLimitFilter>`.
42+
- area: http
43+
change: |
44+
Fixed an issue where filter chain execution could continue on HTTP streams that had been reset but not yet
45+
destroyed. This could cause use-after-free conditions when filter callbacks were invoked on filters that
46+
had already received ``onDestroy()``. The fix ensures that ``decodeHeaders()``, ``decodeData()``,
47+
``decodeTrailers()``, and ``decodeMetadata()`` are blocked after a downstream reset.
4248
4349
removed_config_or_runtime:
4450
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`

source/common/http/filter_manager.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,11 @@ void FilterManager::maybeContinueDecoding(StreamDecoderFilters::Iterator continu
573573

574574
void FilterManager::decodeHeaders(ActiveStreamDecoderFilter* filter, RequestHeaderMap& headers,
575575
bool end_stream) {
576+
// If the stream has been reset, do not process any more frames.
577+
if (stopDecoderFilterChain()) {
578+
return;
579+
}
580+
576581
// Headers filter iteration should always start with the next filter if available.
577582
StreamDecoderFilters::Iterator entry =
578583
commonDecodePrefix(filter, FilterIterationStartState::AlwaysStartFromNext);
@@ -881,6 +886,11 @@ void FilterManager::decodeMetadata(ActiveStreamDecoderFilter* filter, MetadataMa
881886
ScopeTrackerScopeState scope(&*this, dispatcher_);
882887
filter_manager_callbacks_.resetIdleTimer();
883888

889+
// If the stream has been reset, do not process any more frames.
890+
if (stopDecoderFilterChain()) {
891+
return;
892+
}
893+
884894
// Filter iteration may start at the current filter.
885895
StreamDecoderFilters::Iterator entry =
886896
commonDecodePrefix(filter, FilterIterationStartState::CanStartFromCurrent);

source/common/http/filter_manager.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,12 @@ class FilterManager : public ScopeTrackedObject, Logger::Loggable<Logger::Id::ht
10941094
return request_metadata_map_vector_.get();
10951095
}
10961096

1097-
bool stopDecoderFilterChain() { return state_.decoder_filter_chain_aborted_; }
1097+
// Returns true if the decoder filter chain should not process any more frames.
1098+
// This includes cases where the chain was explicitly aborted (e.g., local reply)
1099+
// or where the downstream connection has been reset.
1100+
bool stopDecoderFilterChain() {
1101+
return state_.decoder_filter_chain_aborted_ || state_.saw_downstream_reset_;
1102+
}
10981103

10991104
bool stopEncoderFilterChain() { return state_.encoder_filter_chain_aborted_; }
11001105

test/common/http/filter_manager_test.cc

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,191 @@ TEST_F(FilterManagerTest, IdleTimerResets) {
791791
filter_1->decoder_callbacks_->encodeTrailers(std::move(basic_resp_trailers));
792792
filter_manager_->destroyFilters();
793793
}
794+
795+
// Verify that decodeData is not called on filters after the stream has been reset.
796+
TEST_F(FilterManagerTest, DecodeDataNotCalledAfterDownstreamReset) {
797+
initialize();
798+
799+
std::shared_ptr<MockStreamDecoderFilter> filter(new NiceMock<MockStreamDecoderFilter>());
800+
801+
EXPECT_CALL(filter_factory_, createFilterChain(_))
802+
.WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> bool {
803+
auto factory = createDecoderFilterFactoryCb(filter);
804+
callbacks.setFilterConfigName("test_filter");
805+
factory(callbacks);
806+
return true;
807+
}));
808+
filter_manager_->createDownstreamFilterChain();
809+
810+
RequestHeaderMapPtr headers{
811+
new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "POST"}}};
812+
ON_CALL(filter_manager_callbacks_, requestHeaders()).WillByDefault(Return(makeOptRef(*headers)));
813+
814+
EXPECT_CALL(*filter, decodeHeaders(_, false)).WillOnce(Return(FilterHeadersStatus::Continue));
815+
filter_manager_->requestHeadersInitialized();
816+
filter_manager_->decodeHeaders(*headers, false);
817+
818+
// Simulate a downstream reset.
819+
filter_manager_->onDownstreamReset();
820+
821+
// After reset, decodeData should not be called on the filter.
822+
EXPECT_CALL(*filter, decodeData(_, _)).Times(0);
823+
824+
Buffer::OwnedImpl data("test_data");
825+
filter_manager_->decodeData(data, false);
826+
827+
filter_manager_->destroyFilters();
828+
}
829+
830+
// Verify that decodeHeaders is not called on filters after the stream has been reset.
831+
TEST_F(FilterManagerTest, DecodeHeadersNotCalledAfterDownstreamReset) {
832+
initialize();
833+
834+
std::shared_ptr<MockStreamDecoderFilter> filter(new NiceMock<MockStreamDecoderFilter>());
835+
836+
EXPECT_CALL(filter_factory_, createFilterChain(_))
837+
.WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> bool {
838+
auto factory = createDecoderFilterFactoryCb(filter);
839+
callbacks.setFilterConfigName("test_filter");
840+
factory(callbacks);
841+
return true;
842+
}));
843+
filter_manager_->createDownstreamFilterChain();
844+
845+
// Simulate a downstream reset before headers are processed.
846+
filter_manager_->onDownstreamReset();
847+
848+
// After reset, decodeHeaders should not be called on the filter.
849+
EXPECT_CALL(*filter, decodeHeaders(_, _)).Times(0);
850+
851+
RequestHeaderMapPtr headers{
852+
new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}};
853+
ON_CALL(filter_manager_callbacks_, requestHeaders()).WillByDefault(Return(makeOptRef(*headers)));
854+
855+
filter_manager_->decodeHeaders(*headers, true);
856+
857+
filter_manager_->destroyFilters();
858+
}
859+
860+
// Verify that decodeTrailers is not called on filters after the stream has been reset.
861+
TEST_F(FilterManagerTest, DecodeTrailersNotCalledAfterDownstreamReset) {
862+
initialize();
863+
864+
std::shared_ptr<MockStreamDecoderFilter> filter(new NiceMock<MockStreamDecoderFilter>());
865+
866+
EXPECT_CALL(filter_factory_, createFilterChain(_))
867+
.WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> bool {
868+
auto factory = createDecoderFilterFactoryCb(filter);
869+
callbacks.setFilterConfigName("test_filter");
870+
factory(callbacks);
871+
return true;
872+
}));
873+
filter_manager_->createDownstreamFilterChain();
874+
875+
RequestHeaderMapPtr headers{
876+
new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "POST"}}};
877+
ON_CALL(filter_manager_callbacks_, requestHeaders()).WillByDefault(Return(makeOptRef(*headers)));
878+
879+
EXPECT_CALL(*filter, decodeHeaders(_, false)).WillOnce(Return(FilterHeadersStatus::Continue));
880+
filter_manager_->requestHeadersInitialized();
881+
filter_manager_->decodeHeaders(*headers, false);
882+
883+
// Simulate a downstream reset.
884+
filter_manager_->onDownstreamReset();
885+
886+
// After reset, decodeTrailers should not be called on the filter.
887+
EXPECT_CALL(*filter, decodeTrailers(_)).Times(0);
888+
889+
RequestTrailerMapPtr trailers{new TestRequestTrailerMapImpl{{"foo", "bar"}}};
890+
ON_CALL(filter_manager_callbacks_, requestTrailers())
891+
.WillByDefault(Return(makeOptRef(*trailers)));
892+
893+
filter_manager_->decodeTrailers(*trailers);
894+
895+
filter_manager_->destroyFilters();
896+
}
897+
898+
// Verify that decodeMetadata is not called on filters after the stream has been reset.
899+
TEST_F(FilterManagerTest, DecodeMetadataNotCalledAfterDownstreamReset) {
900+
initialize();
901+
902+
std::shared_ptr<MockStreamDecoderFilter> filter(new NiceMock<MockStreamDecoderFilter>());
903+
904+
EXPECT_CALL(filter_factory_, createFilterChain(_))
905+
.WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> bool {
906+
auto factory = createDecoderFilterFactoryCb(filter);
907+
callbacks.setFilterConfigName("test_filter");
908+
factory(callbacks);
909+
return true;
910+
}));
911+
filter_manager_->createDownstreamFilterChain();
912+
913+
RequestHeaderMapPtr headers{
914+
new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "POST"}}};
915+
ON_CALL(filter_manager_callbacks_, requestHeaders()).WillByDefault(Return(makeOptRef(*headers)));
916+
917+
EXPECT_CALL(*filter, decodeHeaders(_, false)).WillOnce(Return(FilterHeadersStatus::Continue));
918+
filter_manager_->requestHeadersInitialized();
919+
filter_manager_->decodeHeaders(*headers, false);
920+
921+
// Simulate a downstream reset.
922+
filter_manager_->onDownstreamReset();
923+
924+
// After reset, decodeMetadata should not be called on the filter.
925+
EXPECT_CALL(*filter, decodeMetadata(_)).Times(0);
926+
927+
MetadataMap metadata_map{{"key", "value"}};
928+
filter_manager_->decodeMetadata(metadata_map);
929+
930+
filter_manager_->destroyFilters();
931+
}
932+
933+
// Verify that multiple decode operations are all blocked after downstream reset.
934+
TEST_F(FilterManagerTest, AllDecodeOperationsBlockedAfterDownstreamReset) {
935+
initialize();
936+
937+
std::shared_ptr<MockStreamDecoderFilter> filter(new NiceMock<MockStreamDecoderFilter>());
938+
939+
EXPECT_CALL(filter_factory_, createFilterChain(_))
940+
.WillOnce(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> bool {
941+
auto factory = createDecoderFilterFactoryCb(filter);
942+
callbacks.setFilterConfigName("test_filter");
943+
factory(callbacks);
944+
return true;
945+
}));
946+
filter_manager_->createDownstreamFilterChain();
947+
948+
RequestHeaderMapPtr headers{
949+
new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "POST"}}};
950+
ON_CALL(filter_manager_callbacks_, requestHeaders()).WillByDefault(Return(makeOptRef(*headers)));
951+
952+
EXPECT_CALL(*filter, decodeHeaders(_, false)).WillOnce(Return(FilterHeadersStatus::Continue));
953+
filter_manager_->requestHeadersInitialized();
954+
filter_manager_->decodeHeaders(*headers, false);
955+
956+
// Simulate a downstream reset.
957+
filter_manager_->onDownstreamReset();
958+
959+
// After reset, none of the decode operations should call the filter.
960+
EXPECT_CALL(*filter, decodeData(_, _)).Times(0);
961+
EXPECT_CALL(*filter, decodeTrailers(_)).Times(0);
962+
EXPECT_CALL(*filter, decodeMetadata(_)).Times(0);
963+
964+
// Try all decode operations. None of them should reach the filter.
965+
Buffer::OwnedImpl data("test_data");
966+
filter_manager_->decodeData(data, false);
967+
968+
MetadataMap metadata_map{{"key", "value"}};
969+
filter_manager_->decodeMetadata(metadata_map);
970+
971+
RequestTrailerMapPtr trailers{new TestRequestTrailerMapImpl{{"foo", "bar"}}};
972+
ON_CALL(filter_manager_callbacks_, requestTrailers())
973+
.WillByDefault(Return(makeOptRef(*trailers)));
974+
filter_manager_->decodeTrailers(*trailers);
975+
976+
filter_manager_->destroyFilters();
977+
}
978+
794979
} // namespace
795980
} // namespace Http
796981
} // namespace Envoy

0 commit comments

Comments
 (0)