All advisories
Draft

VHDS Host Subscriptions Grow Without Bound

envoyproxy/envoy

Affected packages

envoy other
Affected versions= c33d01624d5b173b5d6e5c0c0474d480cab69b7b
Patched versionsNot specified

Description

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