Die Prozessor-Sicherheitslücke ist in aller Munde, – was ist eigentlich geschehen? Drei Projektgruppen haben eine Fülle von Angriffsmöglichskeiten ausgetüfftelt, um irgendwie Speicher auszulesen, auf den ein User-Prozess gar nicht zugreifen können dürfte (Bruch der Memory Isolation).

Eine Analyse von Andreas Stiller Andreas Stiller, bislang dienstältester Redakteur in der c't- und heise-online-Redaktion, beschäftigt sich mit Prozessoren, High Performance Computing, hardwarenaher Programmierung, HPC-Programmierung und spannenden wissenschaftlichen Themen wie Gravitationswellen, CERN etc. Auch im wohlverdienten Ruhestand, den er Ende 2017 angetreten hat, kann er natürlich von diesen Themen nicht lassen.

Und dafür verwenden sie eine Eigenschaft der modernen Out-of-Order-Prozessoren, die einfach genau das tun, was ihr Name sagt: nämlich Out of Order zu arbeiten. Um diesem Auftrag gerecht zu werden, müssen sie spekulativ schon mal ein paar Befehle ausführen, die aber möglicherweise im realen Programmfluss bei einer Fehlspekulation doch nicht wirklich ausgeführt werden.

Diese Befehle laden häufig auch vermutlich benötigte Daten in die Caches, aktualisieren die für die Adressberechnung benötigten Translation Lookaside Buffer (TLBs) und treffen weitere spekulative Vorbereitungen – all das bringt im Trefferfall enorme Performancevorteile und ohne das macht Out-of-Order wenig Sinn. Aber in diesen spekulativen Befehlen liegt auch die Crux, denn sie geben Raum für eine ganze Reihe von Angriffsszenarien.

Die Forscher sorgen nun dafür, dass die Spekulation bei einem bestimmten Befehl (bedingter oder indirekter Sprung, Exception etc) immer schief geht und dass die Zeit, bis der Prozessor die fehlerhafte Spekulation erkennt, möglichst lang ist. Dann ist genügend Zeit (zum Teil 100 Takte und mehr), um zahlreiche nachfolgende Befehle „transient“ auszuführen. Das heißt, die transienten Befehle werden nur spekulativ mit internen Registern und nie wirklich mit den Architekturregistern ausgeführt, sie können also auch nie eine Exception generieren, egal welchen Unsinn sie anstellen.

Meltdown: Intel anfällig

Am einfachsten kann man das Problem bei der sogenannten Meltdown-Attacke erkennen. Diese nutzt zwei Dinge aus: eine Spezialität der Intel-Prozessoren (Liste der betroffenen Prozessoren) und eine der üblichen Betriebssysteme wie Linux, Windows macOS ...

Aus Performancegründen mappen diese den Adressraum des Kernels und darüber hinaus den des ganzen (sichtbaren) physischen Speichers in der Seitentabelle jedes User-Prozesses, wenn auch geschützt mit einem Supervisor-Bit, so dass der User-Prozess selbst darauf nicht zugreifen kann. Wenn also ein Befehl wie MOV AL,[RCX] auf eine Kerneladresse zugreift, gibt es eine Exception – doch die dauert. Die Zeit kann man noch verlängern, wenn man vorher dafür sorgt, dass die Seitentabelle nicht im Cache residiert und/oder der TLB zuvor gelöscht wird.

Bis der Prozessor erkannt hat, dass er auf eine verbotene Adresse zugreift, hat er jedenfalls bei den Intel-Prozessoren schon mal AL von eben dieser verbotenen Adresse geladen. Und in der Zwischenzeit kann man transiente Befehle ausführen, die abhängig vom Wert in AL das Cache-Layout ändern. Wie man dieses Cache-Layout dann real auslesen kann, also welche Adressen dort gespeichert sind – dazu gibt es schon seit vielen Jahren bewährte Algorithmen wie Flush-Reload (falls ein clflush-Befehl existiert) oder Evict-Reload (falls nicht), die alle eine möglichst präzise Zeitmessung benötigen. Über diesen „covert Channel“ morst man also ein paar Bits aus der Innenwelt des Prozessors nach draußen. Die Exception selbst fängt man mit den üblichen Exception-Handlern ab und wiederholt dann die ganze Prozedur Adresse für Adresse. Das dauert zwar etwas, aber die Meltdown-Autoren kamen immerhin auf über 500 KByte/s Daten von beliebigen physikalischen Adressen. Okay, so ein paar Stunden braucht man dann schon um etwa 8 GByte nach einem bestimmten Muster zu durchsuchen.

Zuweilen schafften es die Intel-Prozessoren aber, den Registerinhalt von AL zu löschen, bevor dieser transient in ein Cache-Muster übersetzt wird. Falls die Meltdown-Software Null liest, wiederholt sie den Vorgang einfach ein paar Mal. Ist es immer Null, dann ist das adressierte Byte höchstwahrscheinlich tatsächlich Null. Letztlich braucht man für die Meltdown-Kernroutine nur ein paar Zeilen.

Meltdown-Angriff: Schematische Darstellung (Bild: Meltdown-Paper )

Die Abhilfe namens KAISER, die jetzt hektisch in die Betriebssysteme eingebaut wurde, besteht vor allem darin, auf das Mapping des kompletten Kernel-Adressraums in den Seitentabellen der User-Prozesse zu verzichten und nur noch die absolut notwendigen Interrupt-Einsprünge vorzusehen. Das läuft jetzt unter Linux unter Kernel Page-table Isolation (KPTI). Hierbei sind dann noch weitere Maßnahmen erforderlich, um nicht allein über die Interrupt-Adressen doch noch das Kernel-Layout bei möglicher Adressverwürfelung (KASLR) herauszuknobeln.

Besser und weit performanter wäre es, so die Autoren, wenn zukünftige Prozessoren und Betriebssysteme optional einen Hardsplit vorsähen, der den gesamten virtuellen Adressraum fest in zwei Hälften aufspaltet: User- und Kernel-Bereich. Dann könnte der Prozessor in Nullkommanichts an der Adresse erkennen, ob hier eine Zugriffsverletzung vorliegt oder nicht und dann ist der Meltdown-Weg dicht. Intel sollte darüber hinaus auch über Änderungen im spekulativen Verhalten nachdenken, etwa das spekulative Laden von spekulativen Adressen einzuschränken. Die AMD- und ARM-Prozessoren scheinen hier ja nicht anfällig zu sein.