senn-techsenn-tech
Security
Security2026-10-11· Von Franz Senn

Zwei vLLM-Advisories zum Cache und was Betreiber eines eigenen AI-Stacks daraus mitnehmen

Ende September 2026 hat das vLLM-Team zwei Sicherheitsmeldungen publiziert, die am 5. Oktober 2026 erneut aktualisiert wurden: GHSA-ph3r-5jfg-f84f mit der CVE-Nummer CVE-2026-105753 und GHSA-935w-9g4m-p28p mit CVE-2026-105752. Der Zeitpunkt hängt davon ab, welche Ebene der Meldeplattform man abfragt, und wir nennen beide: Die Advisory im Repository trägt 28. September 2026 als Anlage und 5. Oktober 2026 als letzte Änderung, derselbe Eintrag in der globalen Sicherheitsdatenbank derselben Plattform steht auf 6. Oktober 2026, 00:02 UTC. Maßgeblich für diesen Text ist die Repository-Fassung, weil sie den Meldeinhalt trägt. Beide sind in aktuellen Versionen behoben, beide führen keinen Code aus. Für Betreiber eigener Inferenz-Flotten beschreiben sie trotzdem zwei Stellen, die man kennen sollte, denn beide sitzen in Mechanismen, die jede geteilte Instanz standardmäßig einschaltet: dem Multimodal-Cache und dem geteilten Prefix-Cache. Beide Funde stammen laut Meldezeile aus dem Programm Patch the Planet, einer Zusammenarbeit von Trail of Bits und OpenAI, entdeckt mit dem Modell GPT-5.5-Cyber.

So vergiftet eine abgelehnte Anfrage den Multimodal-CacheAnfrage mit BildPrompt wird gerendertAdmission lehnt abLänge über max_model_lenVorderseite bleibtvergiftetMetadaten behaupten: Bildist gecachtSpäter dieselbeBilddateiTreffer auf der Vorderseite,kein Payload an den KernAssertion imEngine-KernMeldung: Expected a cacheditem
Ablauf nach Advisory GHSA-ph3r-5jfg-f84f. Die Frontend-Seite führt nur Metadaten, der Engine-Kern die Nutzdaten. Wird eine Anfrage nach dem Rendern abgelehnt, bleibt die Vorderseite mit einem Eintrag zurück, den es hinten nie gab. (Quelle: Advisory GHSA-ph3r-5jfg-f84f, vLLM, 28.9.2026)

CVE-2026-105753: Eine abgelehnte Anfrage genügt für den Ausfall

vLLM führt den Multimodal-Cache standardmäßig gespiegelt über zwei Prozesse: Das Frontend nennt das Advisory P0, dort liegen die Metadaten, den Engine-Kern P1, dort liegen die Nutzdaten. Der Entwurf setzt voraus, dass beide Seiten den Cache zu jedem Zeitpunkt im Gleichschritt führen, damit P0 ohne Rückfrage behaupten darf, ein Medium liege schon in P1.

Diese Annahme bricht in einem bestimmten Fenster. Eine Chat-Anfrage mit Bild wird erst gerendert und dabei in P0 eingetragen, die Längenprüfung gegen max_model_len kommt danach. Fällt die Prüfung negativ aus, wird die Anfrage abgelehnt, der P0-Eintrag bleibt. P0 hält das Bild nun für gecacht, P1 hat es nie gesehen. Die nächste Anfrage, die dasselbe Bild schickt, trifft in P0 und bekommt deshalb kein Payload mitgegeben, nur den Verweis auf den Cache. P1, das nichts hat, läuft in die Zeile assert mm_item is not None. Das Advisory führt das auf alle Versionen vor 0.28.0 mit der Standardkonfiguration mm_processor_cache_type="lru"; die Ausweichpfade processor_only, ausgeschalteter Cache und shm sind nach Angabe des Advisories nicht betroffen.

Die Schwere liegt bei CVSS 6,5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H). Der Buchstabe A:H ist der Punkt: Es ist rein die Verfügbarkeit. Im vom Advisory geprüften Stand fängt vLLM die Assertion als Fehler dieser einen Anfrage ab, öffentliche Berichte zeigen dieselbe Assertion in anderen Ständen als Kaskade weiterer Engine-Fehler. Der gemeldete Effekt ist ein Dienstabfall, den jeder wiederholen kann, der die API benutzen darf. Die im Advisory vorgeschlagene Behebung (atomare Übernahme in beiden Caches nach allen Admission-Prüfungen, Rücknahme des P0-Eintrags bei Ablehnung und die Assertion als behandelter Fehler pro Anfrage) steht als Vorschlag im Advisory-Text; der verlinkte Pull Request #51897 wurde geschlossen, ohne gemerged zu werden. Upstream ist das Problem trotzdem kleiner geworden, das zeigen die Dateien selbst, dazu unten mehr.

CVE-2026-105752: Der Salt gilt nur für die erste Runde

Der geteilte Prefix-Cache war schon einmal eine gemeldete Schwachstelle: CVE-2025-46570 aus dem Mai 2025 beschrieb das Timing-Orakel, über das ein Mieter erraten kann, ob ein Prompt schon im Cache liegt. Das dokumentierte Gegenmittel heißt cache_salt: Jeder Mieter setzt einen eigenen Wert, daraus folgt ein eigener Cache-Namensraum.

Genau diese Trennung löst sich auf dem Antworten-Pfad POST /v1/responses in Teilen auf. Diese Schnittstelle nutzt das Modellformat Harmony (die GPT-OSS-Familie) und fährt Anfragen mit eingebundenen Werkzeugen als Mehrzug-Schleife: Nach jedem Werkzeugaufruf baut vLLM den Prompt der nächsten Runde neu und reicht ihn erneut in die Engine. Der erste Durchlauf übergeben den cache_salt des Aufrufers korrekt, die Fortsetzung ruft den Engine-Eingang ohne Salt auf. Die Fortsetzung liegt danach im ungesalzenen, globalen Namensraum, obwohl der Aufrufer Salting eingeschaltet hatte.

Ein zweiter Mieter, der die Post-Werkzeug-Historie hinreichend rekonstruieren kann, reicht dieselbe Fortsetzung ungesalzen ein und liest in der Antwortusage die exakte Zahl gecachter Tokens pro Zug ab. Das ist wieder das Mitgliedschaftsorakel, gegen das cache_salt schützen soll, diesmal aus einer anderen Quelle: Die Antwortusage selbst liefert die exakten Zahlen, eine Zeitmessung braucht es dafür nicht. Die Vorbedingungen sind happig, deshalb steht die Meldung auf low (CVSS 3,1, AC:H): Harmony-Modell, Werkzeugserver aktiviert, Prefix-Caching an (Standard), das Opfer setzt einen Salt und löst mindestens eine Werkzeugfortsetzung aus, und der Angreifer muss die Historie raten können. Behoben ist es seit 0.30.0: Dort steht der Pfad umgeschrieben, der den Salt aus dem Engine-Eingang liest und in den weiteren Schritten mitführt. Der im Advisory vorgeschlagene Patch (#51818) wurde nie gemerged, die Umsetzung lief über die Umstrukturierung.

Der Selbsttest: unsere Lanes am 11. Oktober 2026 nachgemessen

Die erste Frage bei jeder Meldung ist die nach dem eigenen Bestand. Am 11. Oktober 2026 haben wir die vier Inferenz-Endpunkte über ihre eigene API befragt:

  • Primär-Lane auf 192.168.180.3:8000: meldet 0.1.dev20073+g8e685d198
  • Fallback-Lane auf 192.168.180.202:8080: meldet 0.30.0
  • Embedding- und Rerank-Lane auf 192.168.180.211:8010 und :8011: melden 0.30.0, das gezogene Image ist vllm/vllm-openai:v0.30.0

Nach der Lesart der Advisory-Metadaten (betroffen < 0.28.0 und < 0.30.0) sind die drei 0.30.0-Endpunkte bei beiden Meldungen raus. Bei der Primär-Lane sagt die Kennung nichts: Ein Nightly-Baum mit einer dev-Nummer und einem Commit aus unserem eigenen Build-Zweig lässt sich gegen keine Release-Linie vergleichen. Also haben wir in der laufenden Datei nachgesehen statt zu raten, im Container des Produktivimages:

  • In multimodal/cache.py steckt der Fix. Unsere laufende Datei ist deckungsgleich mit dem Release-Tag v0.28.0, beide Prüfsummen sind identisch. Damit steht drin, was 0.28.0 eingeführt hat: die Klasse MultiModalCacheMissError mit fünf Vorkommen, zwei invalidate-Methoden und ein _cache.pop, also der behandelte Fehler samt Rücknahme des Schatteneintrags. Die Assertion Expected a cached item steht trotzdem noch dreimal in der Datei, dreimal auch am Tag v0.28.0. Wer bloß die Assertions auszählt, verfehlt den Fix in beide Richtungen.
  • In responses/serving.py steckt er nicht. Unsere Datei hat 1563 Zeilen, der Tag v0.27.1 hat 1561, die Prüfsummen unterscheiden sich, der Aufbau ist derselbe. Die Fortsetzung steht in Zeile 722 als tokens_input(token_ids) ohne Salt. Der Stand v0.30.0 ist an dieser Stelle umgebaut und gut 180 Zeilen kürzer: Er liest den Salt mit cache_salt = engine_input.get("cache_salt") aus dem Engine-Eingang zurück und gibt ihn beim Neurendern der Harmony-Nachrichten erneut mit. Beides fehlt in unserem Stand.
Vorkommen von MultiModalCacheMissError im Multimodal-Cachev0.27.10 · betroffen laut Advisoryv0.28.05 · Fix-Liniev0.31.09 · code liegt als Unterpaket vorunser Produktiv-Image5 · deckungsgleich mit v0.28.009
Eigene Zählung in den Quelltexten der Release-Tags und im laufenden Container, Stand 11.10.2026. Die Datei vllm/multimodal/cache.py unseres Produktivbilds hat dieselbe Prüfsumme wie der Tag v0.28.0. Ab v0.31.0 liegt derselbe Code als Unterpaket cache/ in fünf Dateien vor, gezählt ist über alle fünf. (Quelle: Eigene Messung, Quelltexte der Tags v0.27.1, v0.28.0, v0.31.0 und der laufende Container)

Der Eigenbau hat also den einen Fix an Bord und den anderen nicht. Die Kennung 0.1.dev20073+g8e685d198 des Images hätte uns dazu in beide Richtungen falsch informiert, zwei Prüfsummen gegen die Upstream-Tags sagen es in Sekunden. Für uns heißt das bei Meldung eins: Der Pfad ist geheilt, wir sind raus. Bei Meldung zwei bleibt der Befund stehen, ohne uns zu treffen, denn der Harmony-Pfad ist bei uns nicht erreichbar, wir betreiben kein GPT-OSS, und das Gateway fragt die Chat-Completions-Schnittstelle ab. Das Produktivbild ist seit der Messung nicht neu gebaut und trägt unverändert das Baudatum 3.10.2026. Nachziehen wäre die klare Antwort, sie ist offen.

Ins Bild passt noch der dritte Eintrag derselben Meldewelle, der unsere Dokumentenverarbeitung betrifft: GHSA-q43m-vhcp-mhvm (CVE-2026-105750) meldet, dass Docling die Schranke enable_local_fetch im HTML-Browser-Rendermodus nicht durchsetzt, betroffen sind 2.82.0 bis vor 2.118.1 und dieselbe Spanne für das Paket docling-slim ab 2.92.0. Unser Docling-Container meldet 2.43.0 und liegt damit außerhalb der betroffenen Spanne, gemessen am 11. Oktober. Zweiter Grund, unabhängig von der Version: Das Advisory schließt die Standardkonfiguration ohne Seitenrendern, die Kommandozeile und docling-serve ausdrücklich aus, und genau so läuft unsere Instanz. Die Meldeplattform führt die Meldung mit 5,9, die NVD-Metadaten mit 7,5, der Unterschied liegt allein am Angriffsaufwand.

Was Betreiber daraus mitnehmen

Die Versionsgrenzen für die eigene Instanz, sortiert nach Exposition:

  • Multimodal-Anfragen von Nutzern: mindestens 0.28.0, aktuell lieber 0.31.0 (erschienen 5.10.2026). Nicht weil die Assertion verschwände, die steht auch dort noch, sondern weil der Drift dort als Fehler behandelt und der Schatteneintrag zurückgenommen wird.
  • Mandantentrennung über cache_salt: mindestens 0.30.0 (erschienen 22.9.2026).
  • Nur Texte, nur intern, kein Werkzeugpfad: an einem reinen Text-Stack laufen beide Meldungen vorbei, ein aktueller Stand bleibt trotzdem die bessere Ausgangslage.

Zwei Punkte jenseits von Versionsnummern. Der erste: cache_salt ist eine Zusage pro Aufruf, kein Schalter am Server. Ein Gateway, das den Wert nicht durchreicht, betreibt ungesalzen, und interne Wiedereinreichungen können denselben Wert verlieren, wie diese Meldung zeigt. Wer Mandanten wirklich trennen will, kommt auf Dauer pro Mandant nicht ohne eigene Instanz oder zumindest einen eigenen Cache-Namensraum. Der zweite: Bei selbst gebauten Images sagt der gelesene Code mehr als die Versionskennung, und zwar in beide Richtungen. Unser Eigenbau ist am 3. Oktober 2026 gebaut und meldet 0.1.dev20073+g8e685d198, also keine Zahl, die man gegen eine Advisory-Spanne rechnen könnte. Nachgesehen haben wir mit zwei Prüfsummen: multimodal/cache.py ist deckungsgleich mit dem Tag v0.28.0 und damit gepatcht, responses/serving.py entspricht dem Aufbau von v0.27.1 und damit nicht. Inhaltlich steht der Baum unserer Datei nach an zwei verschiedenen Stellen, und keine der beiden hätte die Kennung verraten.

Am Rand gehört noch eine Beobachtung festgehalten: Beide Funde stammen aus einem Programm, das maschinelle Suche nach Sicherheitslücken mit menschlichem Bericht verknüpft. Projekte, die ihre Inferenz-Engine selbst patchen, werden die Advisory-Liste des Engines damit so behandeln müssen wie die DSA-Liste des Betriebssystems.

Weiterführende Quellen

Fragen?
Muss ich vLLM jetzt sofort auf 0.31.0 heben?+

Nach Dringlichkeit gestaffelt. Wer multimodale Eingaben von Nutzern annimmt, sollte auf mindestens 0.28.0, besser 0.30.0 oder 0.31.0 gehen, weil dort aus dem Absturz ein behandelter Fehler geworden ist, der den Cache-Eintrag zurücknimmt. Wer mehrere Mandanten über cache_salt trennt, braucht mindestens 0.30.0, sonst gilt die Trennung in Tool-Fortsetzungen nicht. Eine texteigene, intern erreichbare Instanz ohne Tool-Endpunkt hat es weniger eilig. Beide Meldungen sind in ihrer Schwere low und moderate eingeordnet, keine führt Code aus.

Ist cache_salt damit als Schutz unbrauchbar?+

Nein, aber man muss es wie einen Vertrag pro Aufruf behandeln. Das Advisory zu CVE-2026-105752 zeigt eine interne Wiedereinreichung, die den Salt verliert. Gateways und vorgeschaltete Proxys gehören geprüft, ob sie den Wert überhaupt weiterreichen, und die saubere Trennung bleibt die Instanz pro Mandant oder zumindest ein eigener Cache-Namensraum.

Warum reicht die Versionsnummer bei meinem Docker-Image nicht?+

Weil selbst gebaute Images oft eine Nightly-Kennung wie 0.1.devNNNN tragen, die sich gegen keine Release-Linie vergleichen lässt. Bei unserem Image haben wir die zwei betroffenen Code-Stellen direkt in der laufenden Datei gelesen. Das ist zehn Minuten Arbeit und die einzige Aussage, die bei Eigenbauten zählt.