Skip to content

Commit a3f6c1c

Browse files
committed
Fixed an UAF in DNS cache (#1709)
Fix for GHSA-g9vw-6pvx-7gmw Signed-off-by: Yan Avlasov <[email protected]>
1 parent f9a24c0 commit a3f6c1c

File tree

5 files changed

+95
-5
lines changed

5 files changed

+95
-5
lines changed

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ bug_fixes:
110110
change: |
111111
Fixed an issue where cookies prefixed with ``__Secure-`` or ``__Host-`` were not receiving a
112112
Secure attribute.
113+
- area: dns
114+
change: |
115+
Fixed an UAF in DNS cache that can occur when the Host header is modified between the Dynamic Forwarding and Router
116+
filters.
113117
114118
removed_config_or_runtime:
115119
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`

source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,12 +642,16 @@ void DnsCacheImpl::ThreadLocalHostInfo::onHostMapUpdate(
642642
const HostMapUpdateInfoSharedPtr& resolved_host) {
643643
auto host_it = pending_resolutions_.find(resolved_host->host_);
644644
if (host_it != pending_resolutions_.end()) {
645-
for (auto* resolution : host_it->second) {
645+
// Calling the onLoadDnsCacheComplete may trigger more host resolutions adding more elements
646+
// to the `pending_resolutions_` map, potentially invalidating the host_it iterator. So we
647+
// copy the list of handles to a local variable before cleaning up the map.
648+
std::list<LoadDnsCacheEntryHandleImpl*> completed_resolutions(std::move(host_it->second));
649+
pending_resolutions_.erase(host_it);
650+
for (auto* resolution : completed_resolutions) {
646651
auto& callbacks = resolution->callbacks_;
647652
resolution->cancel();
648653
callbacks.onLoadDnsCacheComplete(resolved_host->info_);
649654
}
650-
pending_resolutions_.erase(host_it);
651655
}
652656
}
653657

test/extensions/filters/http/dynamic_forward_proxy/BUILD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ envoy_cc_test_library(
6666
alwayslink = 1,
6767
)
6868

69+
envoy_cc_test_library(
70+
name = "modify_host_filter_lib",
71+
srcs = ["modify_host_filter.cc"],
72+
deps = [
73+
"//envoy/http:filter_interface",
74+
"//envoy/registry",
75+
"//envoy/server:filter_config_interface",
76+
"//source/extensions/filters/http/common:factory_base_lib",
77+
"//source/extensions/filters/http/common:pass_through_filter_lib",
78+
"//test/extensions/filters/http/common:empty_http_filter_config_lib",
79+
"//test/integration/filters:common_lib",
80+
],
81+
)
82+
6983
envoy_extension_cc_test(
7084
name = "proxy_filter_integration_test",
7185
size = "large",
@@ -79,6 +93,7 @@ envoy_extension_cc_test(
7993
# https://gist.github.com/wrowe/a152cb1d12c2f751916122aed39d8517
8094
tags = ["fails_on_clang_cl"],
8195
deps = [
96+
":modify_host_filter_lib",
8297
":test_resolver_lib",
8398
"//source/extensions/clusters/dns:dns_cluster_lib",
8499
"//source/extensions/clusters/dynamic_forward_proxy:cluster",
@@ -114,6 +129,7 @@ envoy_extension_cc_test(
114129
# https://gist.github.com/wrowe/a152cb1d12c2f751916122aed39d8517
115130
tags = ["fails_on_clang_cl"],
116131
deps = [
132+
":modify_host_filter_lib",
117133
":test_resolver_lib",
118134
"//source/extensions/clusters/dns:dns_cluster_lib",
119135
"//source/extensions/clusters/dynamic_forward_proxy:cluster",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include "envoy/http/filter.h"
2+
#include "envoy/registry/registry.h"
3+
#include "envoy/server/filter_config.h"
4+
5+
#include "source/extensions/filters/http/common/factory_base.h"
6+
#include "source/extensions/filters/http/common/pass_through_filter.h"
7+
8+
#include "test/integration/filters/common.h"
9+
10+
namespace Envoy {
11+
12+
class ModifyHostFilter : public Http::PassThroughFilter {
13+
public:
14+
ModifyHostFilter() = default;
15+
Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override {
16+
headers.setHost("non-existing.foo.bar.bats.com");
17+
return Http::FilterHeadersStatus::Continue;
18+
}
19+
};
20+
21+
class ModifyHostFilterFactory : public Extensions::HttpFilters::Common::EmptyHttpDualFilterConfig {
22+
public:
23+
ModifyHostFilterFactory() : EmptyHttpDualFilterConfig("modify-host-filter") {}
24+
absl::StatusOr<Http::FilterFactoryCb>
25+
createDualFilter(const std::string&, Server::Configuration::ServerFactoryContext&) override {
26+
return [](Http::FilterChainFactoryCallbacks& callbacks) -> void {
27+
callbacks.addStreamFilter(std::make_shared<::Envoy::ModifyHostFilter>());
28+
};
29+
}
30+
};
31+
32+
// perform static registration
33+
static Registry::RegisterFactory<ModifyHostFilterFactory,
34+
Server::Configuration::NamedHttpFilterConfigFactory>
35+
register_;
36+
37+
} // namespace Envoy

test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ class ProxyFilterIntegrationTest : public testing::TestWithParam<Network::Addres
112112
bool use_sub_cluster = false, double dns_query_timeout = 5,
113113
bool disable_dns_refresh_on_failure = false,
114114
bool allow_dynamic_host_from_filter_state = false,
115-
const absl::optional<std::string>& prepend_custom_filter_config_yaml = absl::nullopt) {
115+
const absl::optional<std::string>& prepend_custom_filter_config_yaml = absl::nullopt,
116+
bool use_dfp_even_when_cluster_resolves_hosts = false) {
116117
const std::string filter_use_sub_cluster = R"EOF(
117118
name: dynamic_forward_proxy
118119
typed_config:
@@ -147,7 +148,8 @@ name: stream-info-to-headers-filter
147148

148149
if (prepend_custom_filter_config_yaml.has_value()) {
149150
// Prepend DFP filter.
150-
if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.dfp_cluster_resolves_hosts")) {
151+
if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.dfp_cluster_resolves_hosts") ||
152+
use_dfp_even_when_cluster_resolves_hosts) {
151153
config_helper_.prependFilter(use_sub_cluster ? filter_use_sub_cluster
152154
: filter_use_dns_cache);
153155
} else if (use_sub_cluster) {
@@ -162,7 +164,8 @@ name: stream-info-to-headers-filter
162164
// Prepend stream_info_filter.
163165
config_helper_.prependFilter(stream_info_filter_config_str);
164166
} else {
165-
if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.dfp_cluster_resolves_hosts")) {
167+
if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.dfp_cluster_resolves_hosts") ||
168+
use_dfp_even_when_cluster_resolves_hosts) {
166169
config_helper_.prependFilter(use_sub_cluster ? filter_use_sub_cluster
167170
: filter_use_dns_cache);
168171
} else if (use_sub_cluster) {
@@ -1677,5 +1680,31 @@ TEST_P(ProxyFilterIntegrationTest, ResetStreamDuringDnsLookup) {
16771680
EXPECT_EQ("504", response->headers().getStatusValue());
16781681
}
16791682

1683+
// This test validates that processing of DNS resolutions on worker threads is handled correctly.
1684+
// The test uses specific scenario where DFP filter AND async resolution in DFP cluster are enabled.
1685+
// Normally DFP filter is not needed, however this configuration can occur as the
1686+
// envoy.reloadable_features.dfp_cluster_resolves_hosts flag is now enabled by default. The test
1687+
// also requires the Host header to be modified between DFP and Router filters to trigger abnormal
1688+
// behavior in the DNS resolution processing loop.
1689+
TEST_P(ProxyFilterIntegrationTest, DoubleResolution) {
1690+
config_helper_.addRuntimeOverride("envoy.reloadable_features.dfp_cluster_resolves_hosts", "true");
1691+
config_helper_.addRuntimeOverride(
1692+
"envoy.reloadable_features.skip_dns_lookup_for_proxied_requests", "true");
1693+
upstream_tls_ = false;
1694+
autonomous_upstream_ = true;
1695+
// Add DFP filter even if async DNS resolution is enabled.
1696+
config_helper_.prependFilter("{ name: modify-host-filter }");
1697+
initializeWithArgs(1024, 1024, "", typed_dns_resolver_config_, false, 5, false, false,
1698+
absl::nullopt, true);
1699+
1700+
codec_client_ = makeHttpConnection(lookupPort("http"));
1701+
1702+
auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
1703+
1704+
ASSERT_TRUE(response->waitForEndStream());
1705+
// The host modification filter sets a non-existing host which should result in a 503.
1706+
EXPECT_EQ("503", response->headers().getStatusValue());
1707+
}
1708+
16801709
} // namespace
16811710
} // namespace Envoy

0 commit comments

Comments
 (0)