Meltdown: Eine Erklärung für den Laien (oder lesefaulen!)

Intel traf vor kurzem ein sehr bösartiger Bug: MELTDOWN.

Das ganze führte zum Absturz der Intel Aktie von 7%, und wahrscheinlich dazu dass das Internet nicht nur komplett neu gestartet werden muss, sondern auch um 5% langsamer wird…

Aber mal von vorne mit einer Erklärung “Meltdown für Dummies”:

Es spielen hier drei Systeme moderner CPUs (auch AMD und ARM) eine Rolle:

1. Virtueller Speicher: Jeder Speicherzugriff eines Prozesses wird von der CPU Memory Einheit (MMU) umgewandelt auf eine echte Adresse. Außerdem prüft die MMU auch ob der Speicherzugriff erlaubt ist.

2. Cache: Jeder Prozessor hat einen sehr schnellen Speicher direkt neben dem Core – dieser puffert den Inhalt des Speichers, somit kann häufig gelesener Speicher schneller gelesen werden – sogar ohne Zugriff auf den Memory Bus.

3. Out of Order Execution: Moderne CPUs führen mehrere Programmzeilen gleichzeitig aus, auch welche die eventuell garnicht dran kommen oder erlaubt sind.

So, und nun zur Pseudo Erklärung:

Mein Prozess hat seinen eigenen Speicher (fiktive Werte!!) der geht von 0x00000000 – 0x80000000 und gleichzeitig auch den kompletten (geheimen) Kernel Speicher von 0x80000001 – 0xFFFFFFF. Die MMU verbietet mir aber (als Userspace Prozess ohne Systemrechte) jeglichen Lesezugriff auf alle Speicherseiten ab 0x80000001. So muss das sein.

Jetzt macht mein Programm folgendes:

1. Leere den kompletten Prozessor-Speichercache.

2. Erzeuge dir eine legalen Speicherbereich, z.B. von 0x10000000 und nenne diesen A.

3. Lese von der verbotenen Adresse 0x80001000, das Ergebnis ist B. Nehmen wir an wir bekämen hier: 0x25

-> ANMERKUNG: Der Schritt 3 wird ausgeführt werden (Out of Order Execution) aber das Ergebnis wird nicht zurückgegeben werden weil das lesen dieser Adresse ja verboten ist.

4. Schreibe das Ergebnis an die Adresse A + B, also in unserem Fall 0x10000025. Auch diese Anweisung ist nicht erlaubt, wird aber (Out of Order Execution) ausgeführt werden.​

5. Nun lesen wir von Adresse 0x10000000 und ​schauen wir schnell die Antwort kommt – kommt sie sehr schnell so war B und damit der Inhalt der verbotenen Adresse 0x00.

6. Jetzt fangen wir wieder bei 1 an, nur lesen wir diesmal im Punkt 5 nicht von 0x10000000 sondern von 0x10000001 und messen auch hier wieder die Zeit.

7. Wir erhöhen jetzt jedesmal die Adresse um 1, bis wir irgendwann feststellen dass wir bei 0x10000025 plötzlich sehr schnell lesen können.

Damit wissen wir also: B war 0x25 – weil die CPU die entsprechende Adresse des Speichers im Cache hat!​

In der Realität wird übrigens bei Punkt 4 nicht einfach A+B gerechnet sondern es wird multipliziert, also eher A+(B*4096) – weil die CPU immer Page weiße cacht, also 4096 Bytes auf einmal. Bei Punkt 6 wird dann entsprechend nicht um 1 erhöht sondern um 4096 – das Prinzip ist aber das gleiche.

Die Spectre Attacke läuft nach einem ähnlichen Schema ab, nur wird hier der Speicher eines anderen Prozesses ausgelesen indem man ihn dazu bringt von einer verbotenen Adresse mit Out of Order Execution zu lesen.
Genaugenommen führt man extra noch eine Abfrage im Programmcode so aus dass sie immer mit “true” angesprungen wird. Wenn man dann irgendwann mal mit “false” als Ergebnis daher kommt kann man darauf vertrauen dass die sogenante Branch prediction des Prozessors trotzdem den “true” Zweig ausführen wird, weil sie erwartet dass wieder true rauskommt obwohl es diesmal nicht der Fall ist. Man kann also relativ sicher die Out of Order Execution des Prozessors dazu bringen eine bestimmte Zeile auszuführen obwohl es nicht erlaubt ist. Wenn in dieser Zeile eine von uns kontrollierte Speicheradresse vorkommt (per Eingabe die das Programm erlaubt) habe ich über den Cache die Möglichkeit zu sehen an welcher Adresse genau gelesen wurde, also im Prinzip das gleiche wie die Meltdown Attacke.

Der Fehler konnte so nur bei Intel CPUs nachgestellt werden. Bei ARM und AMD CPUs war der Fehler nicht nachvollziehbar.

Die Lösung des Linux Kernels Teams ist übrigens dass jedes Mal bevor ein Programm läuft alle Kernel-Pages aus der MMU gelöscht werden, somit ist kein Zugriff mehr auf diese möglich (auch nicht per Out Of Order Execution) da die MMU selbst die physikalischen Adressen nicht mehr kennt.

Der sogenannte KPTI /KAISER Patch (kernel address isolation to have side-channels efficiently removed) hat aber einen Performance Verlust von 5% (neue CPUs) bis 30% (alte CPUs). Unklar ist bislang ob dieser Performance Verlust auch (unnötigerweise) bei AMD CPUs auftritt. Denn AMD CPUs brauchen den Patch ja eigentlich nicht.

Microsoft hat das Problem wohl schon in Windows gepatcht. Wie genau weiß man aber nicht.

Quelle Meltdown: https://meltdownattack.com/meltdown.pdf​

Quelle Spectre: https://spectreattack.com/spectre.pdf​

14 Antworten auf „Meltdown: Eine Erklärung für den Laien (oder lesefaulen!)“

  1. “Es spielen hier drei Systeme moderner CPUs (auch AMD und ARM) eine Rolle”

    Auch POWER ist betroffen, SPARC mindestens von (Teilen von) Spectre. Spekuliert wird noch, ob sogar die alten VAX-Rechner betroffen sind, weil x86 sich da einiges abgeguckt hat.

  2. Also erstes Mal: Danke. Ich bin kein riesengroßer Fachmann und habe nur Grundkenntnisse über Prozessorarchitekturen aus dem Studium, aber mir war die Erklärung in den Massenmedien nicht genau genug und ich habe jetzt spontan nach einer Erklärung von Meltdown und Spectre gegoogelt und diesen Link gefunden.

    Darf ich mir die Frage erlauben, in wie fern die von dir erläuterten drei zusammenhängenden “Verursacher” MMU, Cache und OoOE mit dem vielfach zitierten “speculative executing” zusammenhängen? (ich habe so Hacke mal Pi verstanden, was das Ziel von speculative executing ist) Cache und MMU hängen ja erstmal nicht unmittelbar damit zusammen, die gibt es ja auch ohne spekulative Operationen, also nehme ich mal an, dass es vor allem mit den out of order executions zusammenhängt. Gibt es diese OoOE’s nur wegen speculative executing oder wie ist das?

  3. OoOE bedeutet ja dass der Prozessor Befehle ausführt die erst später kommen. Heißt also:
    Zeile:
    10 a=1
    20 b=2
    30 c=b+2
    40 d=0

    -> Während ich also bei Zeile 10 gerade tatsächlich bin, führt die CPU schonmal Zeile 20 und Zeile 40 aus. Zeile 30 wird auch vorbereitet, kann aber nicht ausgeführt werden wir dazu ja b brauchen welches erst in Zeile 20 definiert wird. Das macht aber nichts denn das vorbereiten einer Zeile kann trotzdem schonmal erfolgen.

    Die Branch Prediction kommt nun in folgendem Fall vor:
    10 a=0
    20 wenn a=0 dann gehe zu 140
    30 b=2
    40 f=5
    .
    .
    140 c=1
    150 e=6

    Die CPU muss hier nun entscheiden was wahrscheinlicher ist. Wird der Sprung zu Zeile 140 genommen, so muss ich die Befehle ab Zeile 140 schonmal laden, wird der Sprung nicht genommen so muss ich ab Zeile 30 weiter laden. Die Branch Prediction probiert nun zu ermitteln ob gesprungen wird oder nicht. Bei Spectre ist ein Trick dass man absichtlich den Sprung immer nimmt (oder immer nicht nimmt) um damit die Branch Prediction zu trainieren, so erwartet diese beim nächsten Mal auch dass wieder das gleiche passiert. Damit stellt man eigentlich nur sicher dass die Zeile welche man ausführen will auch von der Out Of Order Execution ausgeführt wird, weil ja die Branch Prediction beschlossen hat dass es wahrscheinlich ist dass gesprungen (oder nicht) wird.

    Übrigens: Irrt sich die Branch Prediction so muss die CPU alle vorgeladenen Befehle die die Out of Order Execution schon geladen hat wieder verwerfen. Ein sogenannter Pipeline Flush. Das kostet Zeit die man natürlich nicht verschwenden will.

    Übrigens 2: Mit ‘perf stat’ kann man unter Linux sehen wie gut die Branch Prediction arbeitet:
    644,818 instructions # 0.30 insns per cycle
    131,663 branches # 146.950 M/sec
    7,065 branch-misses # 5.37% of all branches

    Wenn du noch weitere Fragen hast, gerne her damit.

    1. Hallo,

      ja, ich denke ich hab es so verstanden, dass es für meine Kenntnisse und den Wissensdurst ausreicht 🙂 Danke!

      Das Grundziel des Angriffes ist also im Prinzip, via out of order executions “verbotene” Kernel-Speicherzugriffe auszuführen und deren – “offiziell” nicht zurückgegebenes – Ergebnis mit einer weiteren OOE mit einen “legalen” Adresswert zu kaschieren bzw. in einem legalen Adresswert zu “verstecken”. Über “brut force” (einfach hochzählen) abprüfen der legalen Adressbereiche, und dem gleichzeitigen Messen der Speicherzugriffszeit, finde ich irgendwann an der richtigen Adresse raus, dass diese Adresse im Cache gespeichert war und kann damit auf das Ergebnis der – eigentlich unzulässigen – Operation schlussfolgern.

      Die Branch Prediction wird dann quasi nur “missbraucht”, um den Prozessor zu zwingen, meine illegalen Befehle auch wirklich als OOE auszuführen.

      Stimmt das soweit?^^

  4. Hallo Thomas, erstmal vielen Dank für die sehr anschauliche Erklärung! Eine Frage habe ich, warum führt denn die MMU überhaupt Zugriffe auf unerlaubte Speicherbereiche aus, selbst wenn deren Inhalte nicht zurückgegeben werden?

  5. Hallo Sven,

    genau dass ist ja der Fehler den Intel gemacht hat.
    Die Out of Order Execution kann diese Zugriffe durchführen, obwohl der eigentliche Prozess dies eigentlich nicht dürfte. Es ist quasi so dass die Out of Order Execution die Ergebnisse vorbereitet und erst dann wenn der Prozessor tatsächlich an die jeweilige Zeile kommt erst getestet wird ob diese Ergebnisse verwendet werden dürfen.
    Prinzipiell müsste bei der Out of Order Execution eine Rechteprüfung stattfinden – aber diese würde natürlich Zeit kosten und somit die Performance der OoOE verringern.
    Außerdem würde dann beim erreichen der Zeile nochmal eine Rechteprüfung stattfinden müssen -> Doppelte Arbeit.

    Alternativ müsste man die nicht erlaubten Ergebnisse aus dem Prozessorcache entfernen. Auch das kostet aber Zeit.

    Dass Intel beides nicht gemacht hat ist auf Performance Gründen nachvollziehbar. Es ist kein wirklicher Fehler das nicht erlaubte im Cache zu haben, denn die Wahrscheinlichkeit dass das Betriebssystem selbst bald dort drauf zugreifen wird ist relativ hoch. (Prozess will drauf zugreifen, darfs nicht, Betriebssystem wird aufgerufen, greift drauf zu und gibt die Daten dem Prozess – wenn dieser die nötigen Rechte hat!)

    Das große Problem jetzt scheint zu sein dass Intel diese Fehler direkt in die Hardware eingebaut hat – obwohl Prozessoren heutzutage selber nur programmiert sind (mit Microcode) gibt es eben doch Teile die direkt verdrahtet sind und auch vom Microcode nicht verändert werden können.

    Es ist also so dass dieser Fehler nicht unbedingt wirklich ein Fehler ist. Das Auslesen der Cachezugriffsgeschwindigkeit und damit bestimmen der Daten ist ein sogenannter Seitenkanal-Angriff mit dem Intel nicht gerechnet hatte.

    Man hat also quasi die Eingangstür 10 Meter hoch und 3 Meter dick gemacht aber das Fenster offen gelassen 🙂

    Und die einzige Möglichkeit das ganze zu beheben ist nun verschiedene weitere Sicherheitschecks einzubauen, entweder im Prozessor (Microcode) oder im Betriebssystem, aber das wird Performance kosten.

    Oder aber man produziert eine neue Generation Prozessorn die diesen Fehler direkt in Hardware abprüfen. Das kostet wenig bis sehr wenig Performance, aber das Erzeugen einer neuen Prozessorgeneration kann Jahre dauern…

  6. Hallo allerseits,

    also den technischen Ablauf habe ich soweit verstanden. Aber mir ist das alles sehr abstrakt. Ich würde gerne wissen, welche Daten man damit nun tatsächlich auslesen kann und ob man quasi Dateien oder Datensätze auch zusammenhängend konstruieren kann.

    Im Artikel oben steht bei Punkt 7 ja

    “Damit wissen wir also: B war 0x25 – weil die CPU die entsprechende Adresse des Speichers im Cache hat!​”

    Was kann ich aber tatsälich mit 0x25 anfangen?

  7. Hallo Markus,

    ich kann das ganze nicht mit einem konkreten Beispiel belegen, aber trotz der sog. KASLR (Kernel Spache Adress Layout Randomization) stehen bestimmte Daten oft an bestimmten Adressen im Kernel Speicher – oder aber man kann diese Adressen relativ einfach rausfinden.

    Die Gefahr die nun davon ausgeht ist dass ein unpriviligierter Prozess alle Daten des Kernels lesen kann. Das sind z.B. Passwörter von Netzwerkverbindungen, Anmeldedaten, eben alles was auf dem Computer abläuft.

    Bei Webhostern wo mehrere Kunden Webseiten oder virtuelle Server auf dem gleichen Hostsystem haben kann es sein dass ein Kunde alle Daten des Betriebssystems und aller anderen Kunden einsehen kann.

    Das ganze ist noch nicht konkrekt (es gibt wohl noch keinen definitiven Angriff der dies ausnutzt) aber es wird wohl bald konkret werden, denn die Gefahr ist definitiv da und real.

    Zum “Glück” kann man aber wohl nur Daten lesen und keine schreiben. Das ist aber (Webhoster mit mehreren Kunden auf einem System) schon schlimm genug!

  8. Thomas,
    Ebenfalls besten Dank für diese Erklärungen. Ich denke die Lücke ist wirklich einzigartig gefährlich für alle “shared” Plattformen, auf die ich meinen eigenen Code hochladen darf (Cloud-Dienste auf shared servern, shared webhosting etc. etc.).

    Darf man im Umkehrschluss vermuten, dass sich die Lücke kaum für einen Angriff auf ein Einzelplatzsystem (meinen Laptop) oder einen dedicated server lohnt – wenn ich es schaffe, dort Code zur Ausführung zu bringen, dann schreibe ich doch lieber gleich einen klassischen Virus? Oder wären Webpages geeignet, Code auf meiner Maschine zur Ausführung zu bringen (Java-Script wenn die Seite angezeigt wird?!) – obwohl diese Muster evtl. durch Virenscanner erkannt werden könnten?

    1. Hallo Reiner,

      es gibt Angriffsbeispiele die Javascript nutzen. Also von einer Webseite ausgenutzt werden können.

      Prinzipiell geht es hier auf deinem lokalen Rechner eher darum dass ein Prozess dadurch seine Rechte ausweiten kann und Zugriff auf Informationen hat die er nicht haben dürfte.

      Somit ist es ein einfacher Weg für Viren und Malware größere Probleme auszulösen. Schlimmstenfalls sogar an Passwörter ranzukommen und somit deinen Computer zu übernehmen!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.