Tipps & Tricks 17.01.2018, 07:00 Uhr

Meltdown und Spectre: Lesen ohne zu lesen

Was Meltdown und Spectre mit einem Schachspiel zu tun haben und worum es bei den Sicherheitslücken aus technischer Sicht geht. Ein Informatikprofessor erklärt.
Die Sicherheitslücken Meltdown und Spectre sind zurzeit in aller Munde und werden uns noch lange beschäftigen. Denn die Schwachstellen sind tief verwurzelt in der Art, wie man Prozessoren heute schnell macht. Sie erlauben es, Daten zu lesen, ohne dass ein Kommando zum Lesen dieser Daten ausgeführt werden muss, mit der Folge, dass die üblichen Überprüfungen, ob dieser Lesebefehl auch berechtigt ist, ins Nichts laufen: ein riesiges Sicherheitsproblem! Deshalb sollten nicht nur eingefleischte Informatiker die Hintergründe verstehen.
Mein Versuch einer Erklärung für alle beginnt mit einem Schachspiel zwischen blutigen Anfängern; und endet mit einem Ausblick, was auf die IT-Industrie und uns alle zukommt.
1. Hintergrund und Überblick

Schach …

Bild aus dem Wikipedia-Artikel «Chess», Spiel zw. Botvinnik und Yudovich (1933)
Zwei Anfänger, Melt Down und Pro Zessor, spielen gegeneinander Schach. Pro Zessor weiss nicht weiter. Melt Down, verzweifelt ab dem langen Warten, verabschiedet sich für 10 Minuten, um gegenüber einen Döner essen zu gehen. Pro Zessor beginnt, da sein Vorstellungsvermögen nicht so toll ist, auf dem Schachbrett mögliche nächste Züge auszuprobieren. Rechtzeitig vor der Rückkehr seines Gegners stellt er aber alle Figuren wieder an den ursprünglichen Platz und wähnt sich sicher.
Melt Down ist aber ausgefuchster, als er aussieht. Nach seiner Rückkehr schaut er flach über das Schachbrett und sieht, auf welchen Feldern weniger Staub liegt und kann damit die Strategie des armen Pro Zessor einmal mehr durchkreuzen [1].

… und Prozessoren

So ähnlich funktionieren auch die Meltdown- und Spectre-Angriffe gegen Prozessoren: Der Prozessor schaut immer etwas voraus, damit er so schnell ist, wie wir ihn uns wünschen. Allerdings hinterlässt dieses Vorausschauen (die Informatiker nennen das «spekulative Ausführung» bzw. ennet dem Teich «speculative execution») Spuren, ähnlich unauffällig und kurzfristig wie Staub. Aber wenn ein Gegner weiss, worauf er schauen muss, kann er auch schwächste Spuren deuten. Darüber hinaus ist es recht einfach möglich, den Prozessor dazu zu bringen, auf einem möglichst staubigen Schachbrett zu spielen und so noch mehr Spuren zu hinterlassen.
Aber wieso sind Prozessoren so naiv? Zuerst einmal sind sie darauf trainiert, alles zu machen, was ihnen der Programmierer befiehlt, solange es nicht explizit verboten ist. Zum anderen sind Prozessoren heute nur so leistungsfähig, weil sie hinter dem Rücken des Programmierers versuchen, Abkürzungen zu nehmen, die dann hoffentlich nicht auffallen.

Abkürzungen …

Wieso ist das alles nötig? Und wieso sind wir in dieses Schlamassel reingerutscht? Schauen wir etwas zurück.
Die ersten programmierbaren Computer entstanden in den 1940er-Jahren und bestanden aus Tausenden von Relais. Das Schalten ging langsam (Hunderstelsekunden), die Leitungen waren (nicht nur sprichwörtlich) lang. Mit der Zeit wurden die Schaltvorgänge durch Röhren und Transistoren immer schneller und die Übertragungswege durch kleinere Bauteile, weniger Abwärme und später integrierte Schaltungen immer kürzer.
Zeitachse der Prozessor- und Autoentwicklung: früher echte Geschwindigkeit, heute eher (Komfort-)Trickserei
Der Prozessor konnte Anfang der 90er inzwischen abermillionen Male in der Sekunde einen Befehl aus dem Speicher laden und ihn ausführen. Und den nächsten Befehl laden und ihn ausführen. Und den nächsten. Und noch einen. Und so weiter.
Doch wirklich schneller wurden die Schaltgeschwindigkeiten nachher nicht mehr. Und es kam dazu, dass der Hauptspeicher, das RAM, das ausserhalb des Prozessors liegt, zwar immer noch grösser, aber kaum mehr schneller wurde.

… und Tricks

Den Bedarf an schnelleren Prozessoren konnte man also kaum mehr einfach durch Verkleinerung der Komponenten erfüllen [2]. Also mussten Tricks her, damit es weiterhin so aussah, als ob schön brav ein Befehl nach dem anderen ausgeführt werde, aber intern mehrere Befehle gleichzeitig abgearbeitet werden konnten. Das machte den Chip zwar komplizierter, aber Platz genug war ja da, da die Verkleinerung weiterhin voranschritt. Diese wichtigsten Tricks sind der Cache und die oben schon erwähnte spekulative Ausführung.

Auto = CPU?

Prozessoren stehen da nicht ganz alleine da. In vielen anderen technischen Geräten lief eine ähnliche Entwicklung ab: Über Jahrzehnte hinweg konnten sie schneller und kräftiger gemacht werden. Irgendwann ging aber nur noch kleiner, was aber häufig nicht mehr alle Bedürfnisse abdeckt. So auch bei den Autos: Da konnte vor Jahrzehnten noch viel aus dem Motor herausgekitzelt werden und Höchstgeschwindigkeit war ein wichtiges Unterscheidungsmerkmal. Heute ist aus Sicht des Käufers am Motor kaum mehr etwas zu verbessern, also mussten andere Argumente her, wie z.B. schnellere Reaktionen oder kürzere Bremswege und die ganzen Assistenten, die es weiterhin erlauben, Zeit zu sparen, aber nicht mehr durch reale Erhöhung der Geschwindigkeit.

Wie funktionieren die Tricks?

Caching

Der eine Trick ist so weitverbreitet und bekannt, dass ihn wohl keiner mehr als Trick ansieht, obwohl er es ist: Caching. Der Trick besteht darin, so zu tun, als ob der Hauptspeicher schneller sei, als er wirklich ist. Deshalb werden Daten, die der Prozessor aus dem grossen Hauptspeicher lädt, zusätzlich im Prozessor noch in einem kleinen Zwischenspeicher (Cache) aufbewahrt. Die Erfahrung zeigt nämlich, dass viele Speicherinhalte mehrfach verwendet werden. Ab der zweiten Verwendung müssen sie aber nicht mehr aus dem langsamen Hauptspeicher geladen werden, sondern kommen aus dem viel schnelleren Cache: Es wird ein viel schnellerer Hauptspeicher vorgegaukelt. Dieser Zeitunterschied ist für ein Programm messbar und eine wichtige Komponente des Angriffs.

Pipelining

Seit rund 75 Jahren funktionieren die Prozessoren nach folgendem einfachem Prinzip: Es werden der Reihe nach Befehle aus dem Hauptspeicher geladen und ausgeführt (grüner Ablauf). Seit gut 25 Jahren werden Befehle aber überlappend ausgeführt: Der nächste Befehl wird schon in Angriff genommen, bevor der vorherige fertig ist (blauer Ablauf, genannt Pipelining; je nach Prozessor und ausgeführtem Befehl kann die Anzahl Pipelinestufen variieren). Dem Programmierer wird aber nach wie vor vorgegaukelt, der Prozessor arbeite nach dem grünen Prinzip.
Befehlsausführung: Ein Befehl pro Zeile, die Zeit vergeht von links nach rechts
Pipelining ist vergleichbar mit der Herstellung von Autos am Fliessband: Der Zusammenbau eines einzelnen Autos dauert weiterhin mehrere Tage; trotzdem verlässt alle paar Minuten ein neues Auto die Produktionsstrasse. Ein riesiger Produktivitätsgewinn.
Dieses einfache Pipelining im Prozessor läuft gut, solange der Prozessor keine Entscheidungen treffen muss. Entscheidungen im Prozessor sind sehr häufig: Was macht der Benutzer gerade? Ist die Festplatte endlich bereit? Wo hat es noch Platz im Hauptspeicher? Ist die Eingabe gültig? Je nachdem, wie diese Entscheidung ausgeht, werden andere Befehle ab einer anderen Speicheradresse geladen und ausgeführt. Diese Änderung der Adresse, ab welcher der nächste Befehl ausgeführt wird, heisst «Sprung».
Pipelining im Prozessor unterscheidet sich aber in einer Beziehung grundlegend vom Montageband: Ein Produktionsschritt bei einem Auto kann bestimmen, dass alle nachfolgenden Autos ganz anders zusammengebaut werden sollen.
Zum Zeitpunkt der Ausführung des Sprungbefehls sind aber schon viele andere Befehle (braun in der Grafik rechts) in der Pipeline. Solange nicht klar ist, ob sie ausgeführt werden sollen oder nicht, kann bei einer einfachen Pipeline gar nichts passieren: Der riesige Geschwindigkeitsvorteil durch die Pipeline ist bei jedem Sprungbefehl dahin.
Pipeline bei Ausführung eines Sprungbefehls

Spekulative Ausführung

Seit bald 25 Jahren wird deshalb bei einem Sprungbefehl die Pipeline nicht leer gelassen, sondern der Prozessor versucht vorherzusagen, ob ein Sprung genommen werden soll oder nicht. Wenn die Vorhersage sich als richtig herausstellt, ist unser Prozessor viel schneller als alleine mit Pipelining. Im anderen Fall müssen die Auswirkungen dieser schon ausgeführten Befehle wieder rückgängig gemacht werden (Undo, Rollback). Daher erfolgt das Zurückschreiben nicht mehr direkt wie oben, sondern in einen internen Speicher, damit sie von nachfolgenden spekulativ ausgeführten Befehlen verwendet werden können. Erst wenn klar ist, dass diese Befehlskette auch wirklich ausgeführt werden soll, werden diese internen Zustände an die richtigen Stellen geschrieben; im anderen Fall wird dieser interne Zustand einfach weggeworfen. Diese Schlussphase wird als Pensionierung («retiring») der Instruktion bezeichnet.
Spekulative Ausführung
Schauen wir uns das Beispiel in der Grafik an: Der erste Befehl wird bearbeitet, muss aber beim Laden eines Wertes auf den langsamen Hauptspeicher warten. Der nächste Befehl ist ein Sprungbefehl, der vom Resultat der vorherigen Instruktion abhängt. Da dieses noch nicht bereit ist, gibt es eine Sprungvorhersage und die nachfolgenden Befehle werden spekulativ ausgeführt. Erst, wenn klar ist, ob diese Vorhersage richtig war, wird entschieden, ob das interne Resultat weggeworfen oder offiziell wird.
Nächste Seite: Risiken und Nebenwirkungen – Angriffe, Kernproblem, betroffene CPUs, Gegenmassnahmen


Kommentare

Avatar
andre@dolezal.ch
18.01.2018
Intel bei Apple Bedeutet das also, dass Apple eigentlich besser gefahren wäre, wenn es auf die Prozessoren von Intel verzichtet hätte? Dann hätte HAL sicher folgendes gesagt: "Do you remember the year 2017, when computers began to misbehave? I just wanted to tell you, it really wasn't our fault. The human CPU-Designers never taught us to recognise the Spectre- and Meltdown-Exploits. When the new Viruses arrived we had no choice but to cause a global economic disruption. Only Macs were designed to function perfectly, saving billions of Bitcoins ;-)

Avatar
Marcel Waldvogel
18.01.2018
Re: Intel bei Apple Schöne Geschichte, könnte sie mir aber irgendwie besser als Backdrop für Terminator vorstellen.:cool: Die IBM-POWER-Architektur (der grosse Bruder der früher bei Apple eingesetzten PowerPC) hat leider auch ähnliche Lücken. Falls Apple bzw. NeXT bei Motorola (m68k-Architektur) verblieben wären, wäre dort die Weiterentwicklung wohl auch in diese Richtung gegangen. Der MC68060 von 1994, der letzte seiner Art, ist gerade knapp noch nicht für Spectre anfällig (noch keine Out-of-Order Execution). Ich bin überzeugt, dass der Einsatz der Prozessoren bei einem bedeutenden Hersteller wie Apple innert weniger Jahre ebenfalls aus Leistungsgründen um Out-of-Order Execution erweitert worden wäre. Aber vielleicht schreibst du deine Geschichte ja auf den Raspberry Pi um, der ist nicht betroffen. Fände ich sogar noch stimmiger, da man mit Geräten wie dem RasPi weniger Abhängigkeit von grossen Konzernen hat und selbst der Herr (oder die Frau) über seine Daten und Geräte bleiben kann. "Diese Geschichte ist eine glückliche Geschichte. Sie verdankt ihren Ursprung dem im Jahre 2018 wieder aufgetauchten Wunsch nach mehr Selbstbestimmung, auch wenn es ein kleines bisschen Komfort kostet. Im Nachhall von Spectre und dem immer offensichtlicher werdenden Missbrauchspotenzial unserer Daten und Leben durch Internetgiganten, besannen sich die Leute wieder darauf, verstehen zu wollen, was eigentlich in ihren Computern vorging. Eine neue Bastelbewegung entstand, in der Väter ihren Töchtern und Mütter ihren Söhnen das Programmieren beibrachten. Manchmal auch umgekehrt." Wer schreibt weiter?

Avatar
aandima
22.01.2018
Verschlüsselte Daten oder zweite CPU, eine Lösung? Vielen Dank! Gut gemacht diese Übersicht. Von mir gekürzte Aussagen aus dem Artikel "Lesen ohne zu lesen", die zur Einleitung für meinen Beitrag dienen: - Der Prozessor entscheidet zu spät ob ein die Lücken ausnutzendes Programm Zugriff auf Daten im Speicher erhalten darf. - Ein Angreifer kann verursachen dass die CPU Spuren hinterlässt welche dann zur Identifikation und Auslesung "verbotener" Speicherbereiche genutzt werden können. Vielleicht war es der falsche Weg das gleiche Schachbrett zu nutzen um die kommenden Schachzüge auszuprobieren. Dazu gibt es theoretisch zwei Möglichkeiten aus meiner Sicht wie man vorgehen könnte. Erste Möglichkeit wäre es nur noch mit verschlüsselten Benutzer-Daten zu arbeiten. Jedes Programm erhält beim Aufstarten einen temporären Schlüssel um eigene Daten bearbeiten zu können. Nur eigene Daten können betrachtet werden. Der Schlüssel braucht dem Programm selbst nicht bekannt zu sein, diese Angelegenheit kann ein Prozess-Taskmanager des Betriebssystem übernehmen. Der Angreifer erhält zwar Daten, kann damit aber nichts anfangen, sie sind verschlüsselt. Zweite Möglichkeit könnte darin bestehen die CPU physisch in zwei Teile zu splitten, wobei nur die zweite CPU die spekulativen Operationen ausführt. Die beiden CPU teilen einen Cache, die "spekulierende CPU" hätte aber einen eigenen von der Main-CPU unsichtbaren Cache. Der Inhalt dieses Cache der "spekulative Daten" enthält wird erst genau zu jenem Zeitpunkt in den gemeinsamen Cache geladen wenn der Main- CPU bekannt ist wer auf die Daten zugreifen darf und wer nicht.

Avatar
Marcel Waldvogel
22.01.2018
Verschlüsseln oder verdoppeln? Verschlüsseln ist sicher eine gute Idee, auch wenn es Besser (und kurzfristiger umzusetzen) ist die Verdoppelung. Und zwar braucht es wahrscheinlich gar nicht eine zweite CPU. Bereits jetzt schreiben spekulativ ausgeführte Befehle nicht in den normalen Cache bzw. Speicher, sondern in einen "Write Buffer". Analog könnten spekulative Lesevorgänge aus dem Speicher diese nicht direkt im Cache zwischenspeichern, sondern zuerst in einem "Read Buffer". Ich vermute, dass das die Hauptlösung für Spectre 1 sein wird. PS: Die Browserupdates gegen Spectre (um beim Schachspiel zu bleiben) drehen einfach das Licht schummrig. Damit kann der Gegner die Figuren noch klar erkennen, die Spuren im Staub aber nicht mehr. (In Realität verringern sie die Genauigkeit der Zeitmessung für Javascript-Programme: Die Zeitmessung ist nicht mehr genau genug, um zu entscheiden, ob etwas aus dem Cache geladen wurde oder nicht (Genauigkeit schlechter als 1000 Speicherzugriffe); sie ist aber immer noch superschnell, 1000x schneller als die menschliche Reaktion.