VHDS Host Subscriptions Grow Without Bound
Affected commit: c33d01624d
Sink: source/extensions/config_subscription/grpc/xds_mux/delta_subscription_state.cc:42
Summary
On-demand VHDS converts each previously unseen Host header value into a subscribed virtual-host alias and retains a pending callback. Without control-plane responses, these entries are never bounded, expired, or evicted. An attacker submitting varied Host values grows proxy memory toward OOM.
Detail
At delta_subscription_state.cc:42, each previously unseen resource name (derived from the Host header value) is inserted into requested_resource_state_, declared in delta_subscription_state.h:93 as:
absl::node_hash_map<std::string, ResourceState> requested_resource_state_;
The map grows without bound. At line 42:
requested_resource_state_.insert_or_assign(a, ResourceState::waitingForServer());
Each unique Host value that is not already tracked as a wildcard or ambiguous resource is inserted as a new entry waiting for the server. There is no maximum size, eviction policy, or TTL on these entries. Without a control-plane response acknowledging or removing the resource, the entry persists indefinitely.
Reproduce
#!/bin/bash
set -e
git clone --depth 1 https://github.com/envoyproxy/envoy.git /tmp/envoy-vhds
cd /tmp/envoy-vhds
echo "HEAD: $(git rev-parse HEAD)"
# Verify unbounded map type
echo "=== Unbounded subscription map declaration ==="
grep -n 'requested_resource_state_' source/extensions/config_subscription/grpc/xds_mux/delta_subscription_state.h
# Verify insert without size check
echo ""
echo "=== Resource inserted without limit (delta_subscription_state.cc:35-52) ==="
sed -n '35,52p' source/extensions/config_subscription/grpc/xds_mux/delta_subscription_state.cc
rm -rf /tmp/envoy-vhds
Expected output (line numbers may shift):
HEAD: <resolved-HEAD>
=== Unbounded subscription map declaration ===
93: absl::node_hash_map<std::string, ResourceState> requested_resource_state_;
=== Resource inserted without limit (delta_subscription_state.cc:35-52) ===
if (auto it = wildcard_resource_state_.find(a); it != wildcard_resource_state_.end()) {
requested_resource_state_.insert_or_assign(a, ResourceState::withVersion(it->second));
wildcard_resource_state_.erase(it);
} else if (it = ambiguous_resource_state_.find(a); it != ambiguous_resource_state_.end()) {
requested_resource_state_.insert_or_assign(a, ResourceState::withVersion(it->second));
ambiguous_resource_state_.erase(it);
} else {
requested_resource_state_.insert_or_assign(a, ResourceState::waitingForServer());
}
...
With on-demand VHDS enabled, each request with a unique Host header value that has not been previously seen is inserted into requested_resource_state_. There is no maximum size on this map. An attacker sending requests with randomized Host values grows this map toward OOM.
Credit
Zheng Yu @ Depthfirst