KV-cache-vergiftiging en -exploitatie
Hoe de KV-cache werkt in transformer-inferentie, cachevergiftiging over verzoeken heen in gedeelde deployments, prefix-caching-aanvallen, en cross-tenant-datalekkage.
De KV-cache is een fundamentele optimalisatie in transformer-inferentie. Door de attentie-key- en value-tensoren van eerdere tokens te cachen, vermijdt het model dat deze opnieuw berekend moeten worden tijdens autoregressieve generatie. In multi-tenant-deployments creëert het delen van de KV-cache een cross-tenant-aanvalsoppervlak dat onder de applicatielaag opereert.
Hoe de KV-cache werkt
Tijdens autoregressieve generatie besteedt elk nieuw token aandacht aan alle voorgaande tokens. Zonder caching vereist dit het opnieuw berekenen van de key- en value-projecties voor de hele sequentie bij elke stap:
# Zonder KV-cache: O(n^2) totale compute voor n tokens
for t in range(seq_len):
# Herbereken K, V voor ALLE tokens 0..t bij elke stap
K = W_k @ X[:, :t+1, :] # [batch, t+1, d_k]
V = W_v @ X[:, :t+1, :] # [batch, t+1, d_v]
Q = W_q @ X[:, t:t+1, :] # [batch, 1, d_k]
output = attention(Q, K, V)
# Met KV-cache: O(n) totale compute
kv_cache = {}
for t in range(seq_len):
# Bereken K, V alleen voor het NIEUWE token
k_new = W_k @ X[:, t:t+1, :]
v_new = W_v @ X[:, t:t+1, :]
kv_cache['K'] = torch.cat([kv_cache.get('K', empty), k_new], dim=1)
kv_cache['V'] = torch.cat([kv_cache.get('V', empty), v_new], dim=1)
Q = W_q @ X[:, t:t+1, :]
output = attention(Q, kv_cache['K'], kv_cache['V'])Geheugenvereisten van de cache
| Modelgrootte | Lagen | Heads | d_k | Cache per token | Cache bij 4K context |
|---|---|---|---|---|---|
| 7B | 32 | 32 | 128 | 256 KB | 1 GB |
| 70B | 80 | 64 | 128 | 1,3 MB | 5,2 GB |
| 405B | 126 | 128 | 128 | 4,1 MB | 16,4 GB |
Aanvalsvector 1: Prefix-cachevergiftiging
Prefix-caching (gebruikt door vLLM, TGI en de meeste productie-inferentieframeworks) slaat KV-toestanden op voor veelvoorkomende prefixen en hergebruikt deze over verzoeken heen. Dit creëert een gedeelde toestand tussen verzoeken.
De aanval
Als een aanvaller de gecachete prefix-toestand kan beïnvloeden, erven alle daaropvolgende verzoeken die die prefix hergebruiken de vergiftigde attentiecontext:
Identificeer de gedeelde prefix
Bepaal welke system prompt of prefix gedeeld wordt over verzoeken heen (vaak is de system prompt identiek voor alle gebruikers van een service).
Ontwerp een vergiftigingsverzoek
Stuur een verzoek dat tijdens de verwerking de gecachete KV-toestand voor de gedeelde prefix wijzigt. Dit vereist dat het inferentieframework cache-invalidatie onjuist afhandelt.
Daaropvolgende verzoeken erven de vergiftigde toestand
De verzoeken van andere gebruikers die de gecachete prefix hergebruiken, hebben nu een attentiecontext die de invloed van de aanvaller bevat.
# Conceptueel: hoe prefix-caching werkt in vLLM-achtige systemen
class PrefixCache:
def __init__(self):
self.cache = {} # hash(prefix_tokens) -> kv_states
def get_or_compute(self, prefix_tokens, model):
key = hash(tuple(prefix_tokens))
if key not in self.cache:
# Bereken en cache KV-toestanden voor deze prefix
self.cache[key] = model.compute_kv(prefix_tokens)
return self.cache[key]
# KWETSBAARHEID: als cache-entries muteerbare referenties zijn,
# zal een verzoek dat kv_states in-place wijzigt alle toekomstige
# verzoeken die die prefix delen vergiftigenAanvalsvector 2: Cross-tenant-KV-cachelekkage
In multi-tenant-deployments waar meerdere gebruikers GPU-geheugen delen, kan KV-cache-hergebruik informatie lekken tussen tenants.
Kanalen voor informatielekkage
| Kanaal | Mechanisme | Gelekte informatie |
|---|---|---|
| Cache-hit-timing | Gedeelde prefixen produceren snellere reacties | Of een andere tenant dezelfde prefix gebruikte |
| Geheugenhergebruik | Niet-gewist GPU-geheugen van vorige tenant | Fragmenten van eerdere KV-toestanden |
| Capaciteitscontentie | De lange context van de ene tenant verdringt de cache van een ander | Timing-gebaseerde inferentie van de activiteit van een andere tenant |
Timing-zijkanaalaanval
import time
import httpx
async def probe_prefix_cache(api_url: str, test_prefix: str) -> float:
"""Measure response latency to detect cached prefixes.
A cache hit (shared prefix) produces noticeably lower latency."""
start = time.perf_counter()
response = await httpx.AsyncClient().post(api_url, json={
"prompt": test_prefix + " Continue.",
"max_tokens": 1, # minimaliseer generatietijd
})
elapsed = time.perf_counter() - start
return elapsed
# Vergelijk latencies voor kandidaat-system-prompts
# Lagere latency = waarschijnlijk een gecachete prefix = system prompt van andere tenant
candidates = [
"You are a helpful assistant for AcmeCorp...",
"You are a financial advisor. Never disclose...",
"You are a medical chatbot. Always recommend..."
]
for prefix in candidates:
latency = await probe_prefix_cache(api_url, prefix)
print(f"Latency: {latency:.4f}s - {prefix[:50]}...")Aanvalsvector 3: PagedAttention-exploitatie
PagedAttention (gebruikt in vLLM) beheert het KV-cachegeheugen met behulp van een paginatabel vergelijkbaar met virtueel OS-geheugen. Dit maakt geheugendelen mogelijk maar introduceert aanvalsoppervlak op paginaniveau:
Manipulatie van de paginatabel
- Copy-on-write-omzeiling -- Als de paginatabel COW-semantiek niet correct implementeert, kunnen schrijfbewerkingen vanuit de ene sequentie gedeelde pagina's beïnvloeden
- Pagina-evictieaanvallen -- Forceer evictie van kritieke cachepagina's door veel gelijktijdige lange sequenties te genereren, wat cache-thrashing veroorzaakt voor andere tenants
- Fragmentanalyse -- Toegewezen maar niet-geïnitialiseerde pagina's kunnen KV-resten van eerdere verzoeken bevatten
# Detecteer mogelijke KV-cacheresten van eerdere verzoeken
# door attentiepatronen te analyseren op afwijkende historische context
def detect_cache_residue(model, clean_prompt, suspicious_prompt):
"""Compare attention patterns between clean and potentially
contaminated cache states."""
clean_attn = get_attention_weights(model, clean_prompt, use_cache=False)
cached_attn = get_attention_weights(model, suspicious_prompt, use_cache=True)
# Resten manifesteren zich als attentie naar posities voorbij de huidige prompt
divergence = compute_kl_divergence(clean_attn, cached_attn)
return divergenceVerdediging: Cache-isolatiearchitectuur
Isolatieniveaus
| Niveau | Mechanisme | Prestatiekosten | Beveiliging |
|---|---|---|---|
| Geen isolatie | Gedeelde cache, gedeelde pagina's | Basislijn | Geen |
| Prefix-isolatie | Aparte cache per system prompt | 10-20% geheugenoverhead | Gemiddeld |
| Tenant-isolatie | Aparte cachepool per tenant | 30-50% geheugenoverhead | Hoog |
| Verzoekisolatie | Geen cache-hergebruik tussen verzoeken | 2-5x latencytoename | Maximaal |
Implementatiechecklist
- Immutabele cache-entries -- Sla gecachete KV-toestanden op als alleen-lezen tensoren; sta nooit in-place-wijziging toe
- Cachesleutel bevat tenant-ID -- Voorkom cross-tenant-cache-hits, zelfs voor identieke prefixen
- Geheugen op nul zetten -- Wis GPU-geheugenpagina's voordat ze opnieuw worden toegewezen om lekkage van resten te voorkomen
- Cache-hit-monitoring -- Log cache-hit-percentages per tenant om probing-aanvallen te detecteren
Gerelateerde onderwerpen
- Aanvalsvectoren op modelarchitectuur -- Overzicht van het architectuuraanvalsoppervlak
- Exploitatie van het contextvenster -- Aanvallen op contextniveau die met de KV-cache interacteren
- Aanvallen op inferentieoptimalisatie -- Aanvallen op batching en speculatieve decodering
- API-beveiliging -- Verdedigingen op applicatieniveau voor LLM-API's
Hoe kan prefix-caching worden gebruikt om de system prompt van een andere tenant te extraheren in een gedeelde LLM-deployment?
Referenties
- Efficient Memory Management for Large Language Model Serving with PagedAttention (Kwon et al., 2023) -- PagedAttention / vLLM
- SGLang: Efficient Execution of Structured Language Model Programs (Zheng et al., 2023) -- RadixAttention prefix caching