CVE-2026-6860: Vert.x caps SNI cache growth with bounded LRU eviction
Summary
The patch addresses a remotely triggerable memory exhaustion issue in Eclipse Vert.x by replacing unbounded per-SNI TLS context caches with bounded LRU caches and adding regression tests that verify cache size under repeated unique SNI inputs. The fix materially addresses the root cause: attacker-controlled cache cardinality.
Analysis
Vulnerability
CVE-2026-6860 describes an out-of-memory denial of service in Eclipse Vert.x where remote clients can send many TLS ClientHello messages with distinct SNI hostnames that still match a wildcard certificate, causing repeated allocation and retention of SslContext instances. The vulnerable implementation in SslContextProvider used unbounded ConcurrentHashMap instances for SNI-derived context caching, so cache growth was directly influenced by attacker-controlled hostname diversity rather than a fixed resource budget. The upstream patch is tracked in PR #6102, and the issue context is also reflected in the CVE record.
// Vulnerable behavior: attacker-controlled key space in unbounded maps
new ConcurrentHashMap<>(), new ConcurrentHashMap<>()
...
return sslContextMaps[idx].computeIfAbsent(serverName,
s -> createContext(server, kmf, trustManagers, s, useAlpn));Because each unique serverName could create a new cached SslContext, repeated handshakes with novel names could force unbounded heap retention. This is a classic memory amplification pattern: low-cost network input drives expensive object creation and indefinite caching.
Patch
The patch replaces the unbounded SNI cache with a bounded LRU cache and introduces explicit default limits. In SslContextProvider, the cache now uses LruCache with DEFAULT_SNI_CACHE_SIZE = 16 instead of ConcurrentHashMap. Access is synchronized around the map because the new cache implementation is intentionally not thread-safe. In SslContextManager, the provider cache default is also reduced and centralized as DEFAULT_SSL_CONTEXT_PROVIDER_CACHE_SIZE = 64. A reusable io.vertx.core.impl.utils.LruCache class was added to avoid duplicated ad hoc cache logic.
public static final int DEFAULT_SNI_CACHE_SIZE = 16;
new LruCache<>(DEFAULT_SNI_CACHE_SIZE), new LruCache<>(DEFAULT_SNI_CACHE_SIZE)
...
Map<String, SslContext> ctxMap = sslContextMaps[idx];
synchronized (ctxMap) {
return ctxMap.computeIfAbsent(serverName,
s -> createContext(server, kmf, trustManagers, s, useAlpn));
}The new LruCache is a LinkedHashMap configured for access-order eviction:
public class LruCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LruCache(int maxSize) {
super(8, 0.75f, true);
if (maxSize < 1) {
throw new IllegalArgumentException("Max size must be > 0");
}
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}Regression coverage was added in NetTest to verify that 100 distinct SNI values do not grow the cache beyond DEFAULT_SNI_CACHE_SIZE when SNI is enabled, and remain at zero when SNI is disabled. LruCacheTest also validates eviction semantics. These tests directly exercise the exploit shape described by the vulnerability.
Review
Pros
- The patch addresses the actual resource-exhaustion primitive: unbounded attacker-influenced cache cardinality. Bounding the cache converts an unbounded memory sink into a fixed-size structure.
- The use of LRU eviction is technically appropriate for SNI context reuse because it preserves locality for active hostnames while preventing indefinite retention of cold entries.
- The synchronization added around cache access is necessary because the new
LruCacheis not thread-safe. This preserves correctness aroundcomputeIfAbsentand size accounting. - The tests are source-grounded and meaningful: they simulate many unique SNI values and assert the cache remains capped at
SslContextProvider.DEFAULT_SNI_CACHE_SIZE, which is the key security property. - Refactoring the cache into
io.vertx.core.impl.utils.LruCacheremoves duplicated cache code and standardizes bounded-cache behavior across the TLS path.
Cons
- The patch changes concurrency characteristics by replacing lock-free
ConcurrentHashMapaccess with synchronized blocks on each cache map. This is likely acceptable for small bounded caches, but it may introduce contention on handshake-heavy workloads. - The chosen default
DEFAULT_SNI_CACHE_SIZE = 16is conservative and security-friendly, but it may increaseSslContextchurn for deployments serving many legitimate SNI names. That is a performance tradeoff rather than a security flaw. - The snippets do not show whether the cache size is externally configurable. If it is fixed, operators with large multi-tenant SNI fleets may need follow-up tuning support.
- Eviction bounds retained memory, but it does not reduce the per-request cost of creating new contexts for never-before-seen names. An attacker can still induce repeated context creation work, though no longer unbounded retention. That means the patch is strongest against OOM and weaker against pure CPU churn.
Verdict
Root-cause.
This patch fixes the core design issue by eliminating unbounded SNI cache growth and replacing it with explicit bounded eviction. The vulnerable behavior stemmed from storing attacker-controlled SNI-derived entries in unbounded maps; the patch directly removes that condition. The added tests validate the intended invariant under exploit-like input, which increases confidence that the OOM vector described in NVD and the upstream change set in the Vert.x patch are aligned. Residual performance considerations remain, but the memory-exhaustion root cause is materially addressed.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- DoS.java
Best direct match for CVE-2026-6860 because the issue is a Java-side denial-of-service/resource-exhaustion flaw caused by unbounded allocation and cache growth. This lab is tagged with CWE-400 and focuses on defensive remediation patterns relevant to preventing memory exhaustion and service instability.
- DoS.api
Useful companion lab for learning broader defensive controls against request-driven DoS conditions. While not TLS/SNI-specific, it helps practice rate-limiting, bounded processing, and input-driven abuse prevention patterns that are highly relevant to hardening systems against attacker-controlled allocation pressure.
- Panic DoS.go
Recommended as a secondary resilience lab to build intuition for service-hardening against availability failures. It is not Java-specific, but it reinforces secure coding habits around fail-safe handling, limiting attacker-triggered crash/DoS paths, and designing robust request processing under hostile input conditions.