Add 88 new defect entries to HIGH and MEDIUM tables:
HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002
MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
ovs-0001, onos-0003, odl-0002, jetty-0001
PDF: 976K
2.9 KiB
envoy-0001: PreviousHostsRetryPredicate std::find O(n) per retry attempt → O(n²)
Target: envoyproxy/envoy
Severity: HIGH
CWE: CWE-407 (Inefficient Algorithmic Complexity)
File: source/extensions/retry/host/previous_hosts/previous_hosts.h
Status: PATCHED
Description
PreviousHostsRetryPredicate::shouldSelectAnotherHost() performs std::find on
attempted_hosts_ (a std::vector<HostDescription const*>). This method is
called by the load balancer inside a host-selection retry loop
(ZoneAwareLoadBalancerBase::chooseHost) for every candidate host on every
retry attempt. With R retries and H previously-attempted hosts, the total work
is O(R × H) comparisons — O(n²) in the worst case where max_attempts equals
the cluster size.
| Location | Pattern |
|---|---|
previous_hosts.h:10 |
std::find(attempted_hosts_.begin(), attempted_hosts_.end(), &candidate_host) |
load_balancer_impl.cc:668 |
outer loop: for (size_t i = 0; i < max_attempts; ++i) calling shouldSelectAnotherHost |
thread_aware_lb_impl.cc:232 |
same pattern in thread-aware variant |
Root cause
// previous_hosts.h — O(n) scan per call
bool shouldSelectAnotherHost(const Upstream::Host& candidate_host) override {
return std::find(attempted_hosts_.begin(), attempted_hosts_.end(),
&candidate_host) != attempted_hosts_.end();
}
void onHostAttempted(Upstream::HostDescriptionConstSharedPtr attempted_host) override {
attempted_hosts_.emplace_back(attempted_host.get());
}
private:
std::vector<Upstream::HostDescription const*> attempted_hosts_;
Caller loop in load_balancer_impl.cc:658:
const size_t max_attempts = context ? context->hostSelectionRetryCount() + 1 : 1;
for (size_t i = 0; i < max_attempts; ++i) {
host = chooseHostOnce(context);
if (!host || !context || !context->shouldSelectAnotherHost(*host)) {
return host; // O(attempted_hosts) per iteration
}
}
With a cluster of N=100 endpoints and max_attempts=100, a single load-balancing decision may require up to 100 × 100 = 10,000 pointer comparisons. In a busy proxy handling thousands of requests/second, this compounds.
Fix
Replace attempted_hosts_ vector with absl::flat_hash_set<> or
std::unordered_set<>. Pointer identity is the key, so no custom hash is
needed — pointer addresses hash trivially.
#include "absl/container/flat_hash_set.h"
bool shouldSelectAnotherHost(const Upstream::Host& candidate_host) override {
return attempted_hosts_.contains(&candidate_host); // O(1)
}
void onHostAttempted(Upstream::HostDescriptionConstSharedPtr attempted_host) override {
attempted_hosts_.insert(attempted_host.get()); // O(1) amortized
}
private:
absl::flat_hash_set<Upstream::HostDescription const*> attempted_hosts_;
Ops/ns numbers (Java benchmark)
See defects/envoy/unit/EnvoyTest.java.
At R=100 retries, H=100 hosts: slow ~5050 ops, fast ~100 ops → >50× speedup.