Skip to content

Instantly share code, notes, and snippets.

@hilbix
Last active November 9, 2016 08:26
Show Gist options
  • Save hilbix/9e507197649fd6998db4be2d61a001eb to your computer and use it in GitHub Desktop.
Save hilbix/9e507197649fd6998db4be2d61a001eb to your computer and use it in GitHub Desktop.
Ein nicht ganz fertiges Pamphlet zum Thema Speicherschutz von VMs

Ich arbeite an dem Text gerade, das hier sind Zwischencommits

Leider musste ich den Edit-Vorgang unterbrechen. Keine Ahnung, wann ich wieder dazu komme. Sorry.

Im uneditierten Bereich steht eigentlich schon alles. Bis auf kleinere Details, die man aber leicht lösen kann. Was leider noch fehlt ist die Gesamtbetrachtung, also der Beweis, dass das Verfahren genau das leistet, was es behauptet. Das aber ist lediglich die Nagelprobe, ob das, was hier steht, überhaupt sinnvoll ist. Es ändert nichts an der "Erfindungshöhe". Sprich: All das hier braucht lediglich Transpiration, und keinerlei Inspiration.

VM-Protect

  • Stand 2016-09-10 Autor Valentin Hilbig
  • Folgender Text unterliegt der CLL. Er ist damit unter anderem Public Domain und frei verwendbar von jedermann.
  • Erfindungshöhe des Textes: Trivial (weniger als 15 Minuten Denkzeit, die Details ergeben sich von selbst)

Über mich, Valentin Hilbig

Ich bin lediglich ein dummer Nerd, der sich mit dem Bereich der Cytpo gar nicht beschäftigt. Dehalb kann ich mir in diesem Bereich gar nichts neues ausdenken, sondern nur damit rumspielen, was andere geleistet haben. Als Diplommathematiker verstehe ich allerdings die Hintergründe und kenne mathematische und logische Ansätze gut genug, um die Sicherheit eines solchen Konglomerats zu verstehen und evtl.e Risiken auszumerzen.

Da ich nicht vom Fach bin verzeihe man mir bitte, wenn ich einige Begriffe nicht ganz nach dem Mainstream verwende. Bitte diesen Text mit Hirn lesen, und dadurch die kleineren Schnitzer und Patzer von mir bitte passend korrigeren. Wem es wichtig genug ist kann diesen Text gerne forken, kommentieren, mixen usw. Dank CLL ist das alles zulässig.

Wer mir Änderungen zukommen lässt, stellt diese bitte auch unter die CLL, also macht es Public Domain ohne Copyright, sonst kann ich diese nicht übernehmen.

Der Text kann gerne, auch nur in Auszügen, weiter verwendet werden. Dabei können alle Angaben wie Autorenschaft etc. entfallen. Das Verwertungsrecht ist zu 100% und belilebig freigegeben, jegliche Nutzungsform ist erlaubt. Lediglich das Urheberrecht verbleibt beim Autor bzw. den Autoren, was ja auch gar nicht anders möglich ist. Das Urheberrecht schränkt den Text nur in einer Weise ein: Er darf nicht verwendet werden, um ihn gegen die Autoren selber zu richten. Darunter fällt eigentlich nur eines: Dass man den Text mit einem Copyrigth versieht.

Der Autor ist nur angegeben, damit man einen Ansprechpartner bei Fragen oder Kommentaren hat, und damit dieser Text nicht gänzlich anonym herumgeistert, was jedoch gerne möglich wäre. Die Autorenangabe ist nicht nötig, auch dieser Absatz ist nicht nötig, auch der Hinweis auf die CLL ist nicht nötig. Anders gesagt: Nicht ich bin wichtig, der Text ist es. So wie die Eltern eines Babys weniger wichtig sind als das Baby.

Worum handelt dieser Text?

Es geht hierbei um ein Verfahren, das auf technischer Ebene sicherstellt, dass voneinander durch technische Maßnahmen abgeschottete VMs miteinander auf sichere Weise kommunizieren können, und zwar auf Hardware-Ebene, d. h. schnell und effizient, ohne dass es dabei zu einem Datenleck kommen kann.

Des weitern stellt dieses Verfahren sicher, dass man den verschlüsselten RAM-Inhalt einer VM nicht mehr gegen ihren Willen auslesen kann, auch nicht mit Superkräften, wie sie der Hypervisor besitzt.

Motivation dieses Textes ist, durch dieses Verfahren eine VM von einem Host auf einen anderen Host umzuziehen, ohne dass es dabei eine Schutzlücke gibt, in der z. B. der RAM oder der Zustand der VM ohne deren Wissen ausgelesen werden kann.

Hintergrund

Moderne Architekturen enthalten einen Security-Prozessor, der sicherstellen soll, dass VMs gegeneinander abgeschottet werden. Das wird erreicht, indem der RAM verschlüsselt wird.

Das ist zwar eine gute Idee, aber greift zu kurz um auch hardwareseitig sicherzustellen, dass der RAM-Inhalt einer VM nicht doch noch böswillig ausgelesen wird.

Ein kompromittierter Hypervisor hat immer noch die volle Kontrolle über die VM, kann also die VM kompromittieren und dadurch sorgen, dass diese ihren RAM-Inhalt preisgibt. Es benötigt weitere Schritte, damit sichergestellt ist, dass der Hypervisor diese Kontrolle nicht mehr ausüben kann, aber trotzdem weiter als Hypervisor funktioniert.

Das vorgestellte Verfahren ist die offensichtliche Weiterführung des Konzepts und stellt eine reine technische und offensichtliche Evolution von dem Speicherschutz durch Verschlüsselung dar. Es ist damit nicht selbst patentfähig. Genau das zu zeigen ist das Ziel dieses Textes.

Voraussetzungen

  • Wir haben eine moderne CPU
  • Diese verfügt über kryptografische Erweiterungen wie
    • Security-Processor (SP) der für eine VM die transparente ver- und entschlüsselung des RAM übernimmt und dabei den Schlüssel gegen missbrauch sichert
    • Einen echten kryptografisch sicheren Zufallsgenerator in Hardware auf Registerbreite, d. h. wir können diesen 64-bit weit auslesen
    • Die übliche Von-Neumann-Architektur mit den üblichen Verfahren

Einige Eigenschaften dieser Voraussetzungen werden angenommen:

  • Der Schlüssel, mit dem der RAM der VM verschlüsselt wird, ist nicht auslesbar.
  • Der Schlüssel, mit dem der RAM der VM verschlüsselt wird, ist nicht änderbar.
  • Es kommen keine primitiven Block-Chiphren zum Einsatz, d. h. jede Page wird anders verschlüsselt. Dies stellt der SP sicher, indem er jeder Page einen eigenen, eindeutigen IV zuordnet. Dieser IV wird üblicherweise aus der Position der Page über ein cryptographisch korrektes Verfahren errechnet. Die Position der Page ist dabei kein Bestandteil der bekannten Metadaten der Page, muss also niemandem bekannt sein, außer dem SP, um den IV berechnen zu können.
  • Es kommen keine primitiven Stream-Chiphren zum Einsatz, d. h. Streams verfügen zwangsläufig ebenfalls über einen IV, der aus einer NONCE gebildet wird.

Legende (Sprachgebrauch im Text):

  • VM: Virtuelle Maschine. Sozusagen ein per Software und Hardware emulierter Rechner.
  • Host: Die reale Maschine, auf der VMs ablaufen. Sie verfügt über einen Hypervisor, der die Ressourcen für die VMs einteilt. Der Hypervisor ist meistens in einer besonderen VM gelöst, z. B. Dom0 genannt. Dieser stellt auch die virtualisierten Geräte (Konsole, Laufwerke, Netzwerk usw.) bereit, die die VMs benötigen, um ablaufen zu können.
  • IV: Ein bestimmter langlebiger Wert, der benötigt wird, um die Ent- und Verschlüsselung zu initialisieren. Der IV ist typischerweise für jeden Vorgang fest gewählt, aber für andere Vorgängen unterschiedlich. IVs werden in der Regel aus anderen Metadaten berechnet, auf eine Weise, dass Dritte dies nicht können.
  • NONE: Ein einmalig verwendeter, kurzlebiger und unabhängiger Zufallswert. NONCEs werden nur während einer Tätigkeit verwendet und anschließend weggeworfen. Sie werden normalerweise nicht gespeichert sondern nur zwischengespeichert. Er kann z. B. verwendet werden, um IVs zu initialisieren oder um das gemeinsame Geheimnis für TLS zu bilden.
  • TLS: Transport Level Security. Schützt gegen MitM
  • MitM: Jemand, der Zugang zu der Kommunikation zweier Kommunikationspartner besitzt. Ein öffentlicher Kanal hat eine beliebige Anzahl von MitM. Es wird angenommen, dass der MitM Superkräfte hat, also ihm reicht z. B. nur eine Kommunikationsrichtung zu belauschen oder er beliebig die übertragenen Daten verfälschen kann. Seine Superkräfte gehen nicht so weit, dass er das kryptologische Verfahren ohne die Shared Secrets knacken kann. Allerdings wird er alles, was er sieht, hört oder machen kann, auf Haupt- und Seitenkanälen unternehmen, um an diese Shared Secrets zu kommen.
  • Seitenkanal: Informationen, die die eigentlichen Informationen nicht betreffen, wie z. B. der Zeitpunkt, wann ein Bit ausgesendet wird. Seitenkanäle spielen bei der Betrachutung dieses Textes keine Rolle! Dagegen müssen die eingesetzten Crypto-Architekturen schützen. Das hier vorgestellte Verfahren sichert nicht gegen Seitenbandattacken! Ich bin mir zwar ziemlich sicher, dass es selber keine Seitenbandattacke ermöglicht, aber da ich nicht vom Fach bin kann ich das nicht garantieren, das müssen bitte Dritte überprüfen.
  • Kompromittierung: Ein geschütztes System zu unterwandern um an die geschützten Informationen zu gelangen. Es gibt viele Formen der Kompromittierung. Dieser Text geht davon aus, dass von den verwendeten Komponenten (Hardware, signierter Bootcode etc.) keine Gefahr der Kompromittierung ausgeht. In der Realität ist das natürlich nicht zu halten, aber das sicherzustellen ist nicht Aufgabe des Textes hier. Es geht darum, dass mit normalen Mitteln auf regulärem Weg keine Schutzlücke entsteht, die nicht unter der Kontrolle der VM liegt. Wer die VM durch einen Trick dazu bringen kann, beliebige Informationen beliebig herauszurücken, der hat das Verfahren kompromittiert, aber das ist kein Fehler des Verfahrens, sondern unterliegt ja der (in diesem Fall fehlenden) Kontrolle der VM. An einer Stelle hat das Verfahren eine Lücke, die zusätzliche Annahmen erfordert, damit es sicher wird (Stichwort: Boot der 2. VM). Darauf wird entsprechend dort hingewiesen.

Beispiel heute, ohne Verschlüsselung

Eine VM soll von einem Host auf einen anderen übertragen werden. Und zwar so, dass weder Nutzer der VM noch die VM selbst davon viel mitbekommen.

Ich lasse viele Details weg, wie z. B. man sicherstellt, dass das Netz auf dem Zielsystem verfügbar sein muss, oder wie man dafür sorgt, dass der Spanning-Tree (also das MAC-Layer) rechtzeitig umlernt. Die Live-Migration einer VM ist alles ander als ein regulärer Vorgang, der nach Schema-F funktioniert, da es dafür bisher keine Standardisierung gibt.

Die RAM-Verschlüsselung führt zwangsläufig zu einer klaren Mitwirkungspflicht der VMs, was wiederum bedeutet, dass sich dafür ein Standard etablieren kann, was die Situation wesentlich verbessert.

Aber zuerst müssen wir verstehen, wie es ohne Verschlüsselung läuft, danach sehen wir die Veränderung durch Verschlüsselung, und daraus entstehen die Notwendigkeiten, um den vollständigen Schutz zu gewähren.

Auf dem Zielsystem wird eine weitere VM eingerichtet. Diese verfügt über dieselbe logische Konfiguration wie die Quell-VM, aber sie ist noch angehalten. Die Konfiguration umfasst unter anderem das logische Netzwerk (normalerweise wird auch die MAC-Adresse übernommen) wie auch die Laufwerkskonfiguration. Gehen wir zur Einfachheit davon aus, die Laufwerke auf beiden Seiten (Quelle und Ziel) sind durch äußere Maßnahmen in Echtzeit synchronisiert, z. B. per SAN oder DRBD.

Man könnte jetzt die VM anhalten, den RAM samt CPU-Zustand übertragen, und sie dann auf der anderen Seite weiterlaufen lassen. Bei den heutigen RAM-Größen geht das aber nicht schnell genug, die Netzwerk-Bandbreiten sind einfach zu gering, denn idealerweise möchte man die VM unter 1s lang anhalten.

Deshalb wird der RAM von der Quelle auf das Ziel übertragen, während die Quelle noch aktiv ist. Währenddessen notiert der Hypervisor, welche Pages sich noch ändern. Diese überträgt er anschließend, während er sich die neuen Änderungen während der Zeit notiert. Das wird so lange wiederholt, bis am Ende eines Zyklus

a) entweder ein Schwellenwert unterschritten ist, wieviel RAM noch nachübertragen werden muss,

b) oder eine Maximalzeit für den Vorgang erreicht ist. In diesem Fall kann man wählen, ob der Vorgang abbricht oder zwangsweise weitergeführt wird, wodurch es zu einem längeren Ausfall als erwartet für die VM kommt.

Es gibt hier noch eine weitere Variante. Um dafür zu sorgen, dass weniger RAM sich verändert, kann man auch unnötige oder gerade nicht gebrauchte Dienste kurzfristig auf der VM anhalten, oder diese verlangsamen, z. B. indem man die CPU-Leistung der VM verringert. Gehen wir der Einfachheit halber davon aus, Fall a) liegt vor.

In diesem Zustand wird die VM nun kurz angehalten, der Rest des RAM übertragen, der Prozessorzustand gesichert, ebenfalls übertragen, danach werden noch einige Hypervisor-interne Zustände übertragen und dann kann die VM auf dem Ziel gestartet. Die Quell-VM wird im Zuge vollständig deaktiviert.

Die VM sieht dabei eine kurze Unterbrechung des Betriebs der VM, eine Art "Schluckauf", wie er bei gehosteten VMs aber immer mal wieder passieren kann.

Ist ja eigentlich sehr einfach, mag man denken. FALSCH. Es fehlt hier noch ein klein Wenig, ohne das es nicht wirklich funktioniert.

Das "Time GAP" ist dabei die geringste Sorge, also die Zeit, in der die VM kurz angehalten wurde. Idealerweise ist es so klein, dass es hinsichtlich des normalen VM-Scheduling nicht groß auffällt. Es fehlt zwar der Bruchteil einer Sekunde, aber Applikationen sollten das verkraften.

Aber es gibt weitere Hürden, die genommen werden müssen, damit es zu keinem Crash einer VM führt:

Die neue CPU muss z. B. zur alten kompatibel sein.

Im Idealfall wären beide CPUs identisch. Im Normalfall sind sie das aber nie, denn jeder Computer "tickt" anders. Selbst auf derselben Hardware verhalten sich verschiedene baugleiche CPUs leicht verschieden. Eines, dass sich unter Garantie ändert, ist z. B. der CPU-Takt und damit die RTC (Stichwort: adjtime). Gleiche Mainboards unterscheiden sich zwar nur in wenigen PPM , aber das reicht, um ntpd in einer VM leicht aus dem Takt zu bringen. Zwar ist ntpd recht robust, was das angeht, aber er fährt z. B. eine Langzeitstatistik zu Prognosezwecken (adjtime), die sich nur sehr langsam anpassen würde. Weshalb man Dienste wie ntpd besser von dem Umstand des Move informieren sollte.

Außerdem haben Device-Treiber oft einen eigenen, vom Kernel getrennten, Zustand. Da es der Hypervisor ist, der diese virtuellen Geräte bereitstellt, kann er diese in den richtigen Zustand versetzen. Es kann hier aber trotzdem immer zu kleineren Diskrepanzen kommen, z. B. wenn man die Virtualisierungslösung im laufenden Betrieb upgraden möchte (also eine Seite hochziehen, VMs auf die neue Seite, andere Seite hochziehen, VMs wieder rebalancen).

Deshalb ist es empfehlenswert, dem Kernel mitzuteilen, dass der Treiberzustand sich verändert haben könnte.

Für all das gibt es ein probates Mittel: Die Quell-VM wird mit einem synthetischen ACPI-Suspend-to-RAM-Event angehalten und auf dem Zielsystem mit einem ACPI-Resume wieder aufgeweckt. Damit weiß die VM, dass sie kurz schlafeneschickt wurde und kann entsprechend reagieren. Das ist zwar nicht unbedingt notwendig, aber zu empfehlen, damit z. B. Linux die "Bogomips" der neuen CPU einmessen kann.

Ein anderes Problem sind fehlende CPU-Flags. Manche CPUs haben selbst in der gleichen Familie ein anderes CPU-Flag-Set Wenn sich das Linux-Kernel auf ein Feature eingestellt hat, verkraftet es z. B. nicht, wenn sich dieses ändert.

Deshalb kann man auf VM-Ebene vom Hypervisor auch das Feature-Set der virtuellen CPU einstellen, so dass die VM ein bestimmtes Feature verwendet oder nicht verwendet, unabhängig davon, ob die CPU es unterstützt oder nicht. (Nicht vorhandene CPU-Features können evtl. vom Hypervisor emuliert werden.)

Ändern sich die CPU-Flags, dann kann die VM nicht verschoben werden. Man muss sie auf der Quelle also abschalten und auf dem Ziel wieder neu starten.

So weit, "so einfach".

Auftritt verschlüsseltes RAM

Nun werfen wir verschlüsselten RAM in den Topf. Der RAM-Inhalt der VM ist also verschlüsselt, so dass man ihn eben nicht direkt übertragen kann.

Es gibt noch zwei andere Varianten.

  • Der RAM kann vom Hypervisor entschlüsselt werden. In diesem Fall wird er auf dem Quellsystem entschlüsselt, übertragen und auf dem Zielsystem wieder verschlüsselt. Der Nachteil ist, dass man bei Übertragunsfehlern so prinzipiell an den entschlüsselten RAM-Inhalt kommen könnte. Auch könnten Dritte den Hypervisor (durch Exploits) überreden, den RAM zu entschlüsseln. Solch ein Verfahren wäre also ein recht naives System, in der Sicherheit ungefähr vergleichbar mit der Caesar-Chifre. Sprich, der reale Sicherheitsgewinn ist reine Augenwischerei. Es schützt also nur vor unabsichtlichen Fehlern, gegen einen gut geplanten Angriff auf den RAM-Inhalt der VM schützt dieses Verfahren nicht.

  • Der RAM kann vom Hypervisor selbst nicht entschlüsselt werden, weshalb auf dem Zielsystem derselbe Schlüssel für das RAM verwendet wird. Das führt gleich zu mehreren Problemen.

Erstens muss der Schlüssel irgendwie übertragen werden. Diese Übertragung kann man zwar auf der Ebene des SP durch ein PKI absichern (d. h. SP der Quelle und Ziel haben ihre öffentlichen Schlüssel ausgetauscht), wodurch ein MitM nicht mehr möglich ist. Gehen wir davon aus, dass das korrekt implementiert wurde.

Zweitens sind die Pages mit Hilfe eines IV verschlüsselt, in den die Position des RAM einhergeht. Sinnvollerweise verwendet man dafür nicht die relative RAM-Position in der VM, sondern die physikalische RAM-Position im Host. Das spart einiges an Lookups und Reverse-Lookups in die Page-Tables auf Ebene des SP. D. h. man kann den SP unabhängiger vom Speichercontroller gestalten, was die Komplexität stark verringert. Das bedeutet aber wiederum, der Hypervisor benötigt zusätzliche Informationen über das reale Speicherlayout, weil dieses ja zur Entschlüsselung einer Seite als Meta-Information mit übertragen werden muss, damit der Ziel-SP den richtigen IV berechnen kann um die Seite zu entschlüsseln und wieder mit dem für ihn lokalen IV neu zu verschlüsseln.

Gehen wir einfach davon aus, diese zusätzliche Komplexität ist erfüllt. Dann besteht aber immer noch die Gefahr, dass ein Angreifer von außen, also vorbei an der VM, den RAM-Inhalt entschlüsseln könnte. D. h. die VM kann immer noch nicht die volle Kontrolle über ihren RAM-Inhalt ausüben. Der Angriff sieht wie folgt aus:

  • Man kompromittiert den Hypervisor eines Ziel-Systems.
  • Man sorgt dafür, dass der Ziel-SP den Schlüssel vom Quell-SP bekommt. Beide vertrauen sich ja. Man kommt so übrigens nicht an den Schlüssel, diese sind weiterhin innerhalb der SPs geschützt!
  • Man erhält vom Quell-System den verschlüsselten RAM-Inhalt der VM.

Und jetzt lässt man den Hypervisor auf dem Ziel eine VM starten, die den Schlüssel aus dem SP verwendet. Diese VM kann dann den RAM entschlüsseln und an den Hypervisor herausgeben.

Dass der RAM der VM verschlüsselt ist ist kein Hinderungsgrund, dort Maschinencode abzulegen. Man kann "raten", d. h. man schreibt 1 Byte in den RAM, man weiß zwar nicht was dabei herauskommt, aber das kann man aus der Reaktion der VM erkennen. Da man als Hypervisor aber das Scheduling der VM unter Kontrolle hat kann man ja erkennen, was die Instruktion bewirkt hat. Danach kann man Maschinencode in den CPU-Registern ablegen und in den RAM passend übertragen, und diesen ausführen. Die Routine, die den RAM in die CPU lädt, ist ja nur sehr klein.

Gehen wir also von einer vernünftigen Umsetzung von verschlüsseltem RAM durch den SP aus, also dass der SP sicherstellt, dass es unmöglich ist, ohne Kooperation der VM an den unverschlüsselten RAM-Inhalt zu gelangen. Das bedeutet, dass nur die Quell-VM ihren RAM entschlüsseln kann, eine VM auf dem Zielsystem kann zwar ihren eigenen RAM entschlüsseln, nicht aber den der Quell-VM. Damit kann der Hypervisor also auch nicht den RAM auf das Zielsystem übertragen, denn weder der Hypervisor noch das Zielsystem können mit den verschlüsselten Daten etwas anfangen.

Das bedeutet: Der SP rückt den Schlüssel, mit dem der RAM der VM verschlüsselt ist, nicht heraus. Wie wir im weiteren sehen werden, gibt es dafür noch einige Erfordernisse, damit es keine Schutzlücken gibt, durch die man den RAM auf Umwegen (leicht) entschlüsseln kann. (Es ist allerdings unmöglich das gänzlich zu verhindern, aber der Aufwand, den RAM auszulesen, ist dann so extrem hoch, dass es andere Maßnahmen gibt, die verhindern können, dass jemand das in Realität umsetzt ohne dabei erwischt zu werden.)

Die Unmöglichkeit, den RAM zu entschlüsseln, verhindert effektiv die Übertraung der VM von außen. D. h. ohne Kooperation der VM können wir sie nicht mehr übertragen. Das ist kein Fehler, sondern eine sinnvolle Konsequenz.

Eine VM hat die Kontrolle über ihr RAM. Der Schlüssel, mit dem der RAM verschlüsselt ist, steht nur im SP. Auch die VM soll diesen aus Sicherheitsgründen nicht auslesen können. Der SP kann zwar Mechanismen anbieten, dass man den RAM-Schlüssel einer VM speichern und laden kann, aber dies muss wiederum verschlüsselt passieren, so dass nur der SP selbst diesen Schlüssel ermitteln kann. Damit verlässt der Schlüssel den SP niemals im Klartext, die Hardware stellt also eine nicht durch Softwarefehler unübwerwindbare Barriere.

Tatsächlich ist die Barriere überwindbar, und zwar durch Trial and Error. Anhand von Seitenbandinformationen, nämlich dem Verhalten der CPU wenn sie auf einen Befehl stößt, kann man Details über den unverschlüsselten RAM-Inhalt an einer Speicherposition ermitteln. Verschlüsselter RAM verhindert nicht die unautorisierte Änderung des RAMs einer VM. Man kann eine verschlüsselte VM also weiterhin leicht abstürzen lassen, indem man zufällige Werte in deren RAM schreibt, die durch die Entschlüsselung nochmals verändert werden. Man weiß zwar nicht, was die VM nach der Entschlüsselung sieht, aber das ist egal, die VM stürzt ab. Dies kann man nur durch Speicherschutzmechanismen verhindern, wie kryptographsche Checksummen von Pages. Diese Checksumme muss übrigens ebenfalls verschlüsselt sein, damit man keine Korrelationsattacken ermöglicht (Pages mit gleichem Inhalt haben sonst die gleiche Checksumme). Aber das ist vorerst Zukunftsmusik und nicht Teil dieses Textes. Es sei nur erwähnt, dass nur ein Speicherschutz die theoretischen Attacken über Seitenbandinformationen zum Auslesen des RAM-Inhalts einer VM effektiv so erschweren können, dass diese in der Realität dann wirklich keine Rolle mehr spielen. Der Speicherschutz würde in dieserm Fall auch Angriffe a la RowHammer verhindern, da man ja alle unautorisierten Veränderungen an einem Speicher sofort entdeckt und dadurch ein akuter Angriff rechtzeitig erkannt und verhindert werden kann.

Wie überträgt man also eine VM, wenn sie eine wirklich funktionstüchtige RAM-Verschlüsselung hat?

Die VM hat ja Zugriff auf ihren RAM. Es bleibt einem also nichts anders übrig, als die VM zur Mitarbeit bei der Übertragung des RAMs zu überreden. Damit dabei aber keine weitere Schutzlücke entsteht, darf der RAM-Inhalt der VM diese niemals unverschlüsselt verlassen. D. h. die VM muss nicht nur ihren RAM auslesen, sondern auch verschlüsseln, und nur verschlüsselt übertragen.

Dabei darf nur die Ziel-VM in der Lage sein, die Entschlüsselung vorzunehmen. Es handelt sich also um eine End2End-Verschlüsselung.

Damit wird das oben angemerkte Problem, nämlich die Authentizität der Ziel-VM zu prüfen, vom Hypervisor in die VM verlagert. Die Gefahr, einem Trickbetrüger anheim zu fallen besteht zwar weiterhin, aber mit dem entscheidenden Unterschied, dass dies nicht unerkannt an der VM vorbei vonstatten gehen kann. Rückt die VM dem Falschen ihrem RAM-Inhalt heraus, dann ist das der Fehler der VM. Weiß die VM aber, dass sie den RAM niemals herausgerückt hat, dann kann sie auch sicher sein, dass es niemand anderes ohne ihr Wissen getan hat. Gesicherte Software in der VM kann also genau auf diesen Umstand bauen und z. B. sensible Informationen vor der Übertragung deaktivieren um eine manuelle autorisierte Reaktivierung nach dem Verschieben zu verlangen. Ähnlich wie Passwort-Safes die zwischengespeicherten Passworte aus dem RAM löschen bevor ein Computer in den Hibernate geht (um zu verhindern, dass der sensible RAM-Inhalt im Hibernate-File auf der Platte landet).

Es ist zwar weiterhin der Hypervisor, der die Daten von der Quell-VM an die Ziel-VM weiterleitet, aber die Daten selbst sind durch die VM verschlüsselt, der Hypervisor, oder ein MitM, können mit den Daten nichts anfangen.

Hier gehe ich nicht tiefer auf die Lösung ein, wie die VM ermitteln kann, ob es sich um den korrekten Empfänger handelt. Dafür gibt es viele mögliche Lösungen. Eine Möglichkeit sei hier skizziert: Es ist klar, dass man dem SP vertraut. Anderenfalls brauchen wir das ganze Spiel hier nicht zu treiben. Sinnvollerweise weiß die VM auch nichts über den Host oder den SP, außer dass ein Host da ist und ein SP. Es soll also gar nicht nötig sein, dass die VM überhaupt weiß, auf welchen Hosts sie ablaufen kann. Sinnvollerweise löst man das also auf der Ebene der SPs. D. h. der Quell-SP vertraut dem Ziel-SP. Damit kann die Quell-VM auch der Ziel-VM vertrauen, denn der Ziel-SP stellt sicher, dass die Ziel-VM, in die übertragen werden soll, diejenige darstellt, die die Quell-VM als Ziel-VM aufnehmen soll. Über diese Vertrauenskette kann die Sicherheit gewährleistet werden. Diese Kette wird aus spezieller Hardware (SP) inkl. der entsprechenden signierten Software gebildet, variable (austauschbare, evtl. kompromittierte) Software wie der Hypervisor dürfen dabei keinerlei Rolle spielen.

Die Umsetzung des Konzepts stößt dabei an zwei Grenzen:

  • Es muss einen Weg geben, wie die VM zur Mitarbeit gebracht werden kann. Und zwar so, dass auch ein kompromittierter Hypervisor keine Gelegenheit hat, die VM zu betrügen.

  • Es darf keine direkte Möglichkeit für den Hypervisor geben, an RAM-Inhalte der VM zu kommen. D. h. ohne Mitwirkung kann der Hypervisor keine RAM-Inhalte erhalten, auch nicht durch Zufall oder Fehler.

Ersteres ist relativ einfach umzusetzen, indem alles, was dies betrifft, über den SP laufen muss. D. h. der SP legt die Informationen für die VM in den RAM der VM ab. Da der RAM verschlüsselt ist, hat niemand anderes als die VM darauf Zugriff. Die Signalisierung bzw. Bereitstellung übernimmt ein Mechanismus wie ACPI. Das lässt sich also alles per Software erreichen.

Das Zweite aber bedarf einer Unterstützung in der CPU, wenn der Hypervisor nicht durch den SP bereitgestellt wird. Und das will niemand, denn die Sicherheit der Hardware ist eine Aufgabe, die man sinnvollerweise unabhängig vom Hypervisor gestaltet.

Im Folgenden wird beschrieben, wie solch eine Hardwareunterstützung aussehen kann. Ich behaupte nicht, dass es der einzige Weg ist. Es soll nur beweisen, dass es möglich ist

Mir ist nicht bekannt, ob es so etwas schon gibt oder nicht. Ich habe nicht recherchiert, weil das nicht nötig ist. Die Lösung ist ja offensichtlich, sobald man auf das Problem stößt. Der einzige Aufwand ist zu zeigen, dass diese Lösung auch wirklich stimmt.

Notwendige Hardwareunterstützung

Hypervisoren laufen im sog. Ring -1 in der CPU ab. Ring 0 ist traditionell die Kernel- und Treiberebene, die alle Möglichkeiten der Hardware ausnutzen kann. Um Hypervisoren zu ermöglichen wurde der Ring -1 eingeführt.

Der Ring -1 kontrolliert dabei nicht nur Ring 0, er kann ihn auch vollständig manipulieren. Dadurch ist es aber dem Hypervisor möglich, alle Schutzmechanismen einer VM zu umgehen, da der Ring -1 ja einen freien Zugriff auf den unverschlüsselten RAM der VM bietet.

Ring -2

Deshalb ist es notwendig, (mindestens) einen weiteren Ring einzuführen. Nennen wir ihn Ring -2.

Ring -2 läuft, wie Ring -1, im Kontext der VM ab. Allerdings wird im Ring -2 der RAM-Schlüssel des Hypervisors verwendet, während im Ring -1 der RAM-Schlüssel der VM verwendet wird. Damit kann alles, was im Ring -2 abläuft, zwar weiterhin den Ring -1 kontrollieren (z. B. der VM CPU-Zeit entziehen), aber nicht auf den RAM der VM zugreifen.

Ring -2 kann nicht nur vom Hypervisor verwendet werden, sondern auch von anderen VMs. Somit wird auch eine Kommunikation zwischen VMs möglich.

CPU-Register verschlüsselt

Das alleine reicht aber nicht aus. Zusätzlich zum RAM muss der Inhalt der CPU-Register verschlüsselt werden, damit aus der VM keine sensiblen Informationen erhalten werden können. Anderenfalls wäre der Hypervisor z. B. durch Single-Stepping fähig, einen in der VM ablaufenden kryptographischen Algorithmus zu belauschen. Das darf nicht möglich sein.

Jetzt haben wir allerdings ein Problem. Nämlich wie überträgt der Hypervisor z. B. Daten, wie Platten-IO, in die VM? Wenn die CPU-Register verschlüsselt sind, weiß der Hypervisor ja mangels Key nicht, was er übertragen soll.

Dafür gibt es mehrere mögliche Lösungen. Eine sind Shared-Memory, also Speicherbereiche, die mit einem gemeinsamen Schlüssel verschlüsselt sind. Der SP stellt sicher, dass dieser Speicherbereich nur im richtigen Kontext entschlüsselt zur Verfügung steht.

Aber bevor ich darauf eingehe, ist noch eine weitere Kleinigkeit notwendig.

Das NONCE-Register

Es muss ein zusätzliches Random-Snapshot-Register geschaffen werden (NONCE bzw. SALT genannt), in dem beim Umschalten in den Ring -2 ein Zufallswert abgespeichert wird. Beim Zurückschalten aus dem Ring -2 muss sich in dem Register eben dieser Zufallswert wieder befinden.

Inzwischen befindet sich in jeder CPU (hoffentlich) ein echter kryptographischer Zufallsgenerator. Dieser wird für das NONCE-Register verwendet. Das Register steht auch dem SP zur Verfügung, da dieser es verwendet, um die CPU-Register der VM damit zu verschüsseln. Aufgrund der häufigen Wechsel in den Ring -2 wäre es ohne eine NONCE ein Leichtes, Korrelationsattacken gegen die Registerwerte in der VM zu fahren.

Warum ein Register und kein Value auf dem Stack?

Selbstverständlich kann es auch analog über einen Stack abgewickelt werden. Daten auf einen Stack zu pushen halte ich für eine recht langsame Aktion. Deswegen halte ich es für sinnvoll, dass der Ring -2 einen ganz eigenen Satz von Registern hat, der beim Wechsel in diesen zum Einsatz kommt. Damit kann der Kontextwechsel sehr schnell erfolgen und ohne einen zusätzlichen Speicherzugriff. Idealerweise ist der Code für den Ring -2 so klein, dass er in das Cache passt und damit sehr schnell und evtl. gänzlich ohne "teure" RAM-Zugriffe ablaufen kann.

Der Zugriff auf die Register in der VM kann dem Ring -2 nicht verwehrt werden, da er ja in der Lage sein muss, die VM (und damit ihren RAM-Inhalt samt CPU-Zustand) auf Platte zwischenzusichern. Er muss diese Werte auch differenzieren können, da der Hypervisor die VM ja später wieder herstellen können muss.





Ab hier ist es T.B.D.





DMA mit TLS

Nur SP initialisiert Ring -1

Signalisierung zwischen Ring -2 und Ring -1











Uneditierter Kram folgt











Aber wenn man es jetzt auf die naive Weise macht, also RAM (per ACPI-Event außerhalb der Kontrolle der VM) in der VM abschnorchelt und an den Hypervisor übergibt damit er es überträgt, hat der Hypervisor ja wieder Zugriff auf den unverschlüsselten RAM. Auftritt Bugs, d. h. eine andere VM missbraucht den Hypervisor um den RAM einer anderen VM abzuschnorcheln. Und schon ist die ganze schöne Separierung der VMs perdü.

Das ganze Konzept steht und fällt also mit 2 Eigenschaften:

a) Es muss unmöglich sein, an den RAM-Schlüssel im Security-Processor zu gelangen.

b) Es darf in der VM keinen ACPI-Event außerhalb der Kontrolle der VM geben, der den RAM unverschlüsselt abschnorchelt.

Ersteres ist einfach, letzteres zu erreichen, das ist die Herausforderung, damit steht und fällt das gesamte Sicherheitskonzept!

Wenn man b) nicht löst ist nämlich der gesamte Aufwand für die Katz, man kann eigentlich genausogut unverschlüsselt arbeiten. Oder anders gesagt: Man verschiebt das Problem nur (Sankt-Florians-Prinzip), man löst es nicht wirklich. Zwar wird die Hürde dank Verschlüsselung etwas angehoben, aber nicht so weit, dass man sie nicht leicht überwinden könnte.

Ich denke, genau hier sind wir mit der neuen Hardwarearchitektur des Security-Prozessors angekommen. D. h. die VMs sind zwar gegenseitig geschützt, aber noch nicht gegenüber dem Hypervisor. DAS IST ZIEMLICH SCHADE. Es stellt zwar eine Verbesserung dar, aber nur eine marginale Verbesserung. Software-BUGs außerhalb der VM haben dann also immer noch Einfluss auf die Sicherheit der VM selbst, was ziemlicher Mist ist.

Aber das IST lösbar. Da ich das noch nicht live gesehen habe kann ich nicht sagen, wie es real gemacht wird und ob es überhaupt schon gemacht wird, aber hier ist die Lösung, die ICH dafür sehe (es entspringt also meiner unmaßgeblichen Imagination, ist also reine Wunschfantasie, Du kannst auch sagen, reine Imagination):

Ich für meinen Teil würde folgende Features zusätzlich einführen:

A) Einen Ring -2, der zwar in den Kontext der VM wechselt (Page-Register usw.), aber der nur Zugriff auf den RAM in verschlüsselter Form zulässt, d. h. sobald die CPU Ring -2 betritt, gilt nicht der RAM-Schlüssel von der VM, sondern der RAM-Schlüssel vom Hypervisor. Der Ring -2 ist dabei nicht auf den Hypervisor beschränkt, Ring -2 kann von beliebigen VMs genutzt werden (der Hypervisor ist sogesehen ja auch nur eine andere VM mit Kontrollrechten).

B)

C)

D) Es wird ein DMA mit TLS geschaffen, also mit folgender Eigenschaft: Der RAM wird mit dem Schlüssel desjenigen, der den Transfer initiiert, entschlüsselt, und mit einem Transport-Key verschlüsselt, der sich im RAM des Initiators des Transfers befindet (also mit seinem RAM-Schlüssel verschlüsselt ist), oder entschlüsselt, mit einem Key, der sich im (verschlüsselten) RAM des Ziels befindet. Dabei wird übrigens das neue Zufallsregister ebenfalls verwendet! Zusätzlich werden die Daten mit dem RAM-Schlüssel des Ziels ebenfalls verschlüsselt. Es sieht also so aus: "Decrypt - TLS - Encrypt". Den Ent- und Verschlüsselnden DMA braucht man sowieso, da man sonst ja keine Hardware mehr ansprechen kann (in diesem Fall wird Decrypt oder Encrypt weggelassen, was man in Zukunft vielleicht auch noch verbessern kann, d. h. ich schicke die Ethernet-Frames dann verschlüsselt an die 10 GBit.-Netzwerkkarte).

E) Die volle Kontrolle vom Ring -1 wird dem Hypervisor entzogen. Sprich, man kann den Ring -1 zwar initialisieren ("booten"), aber nur mit einem Stück signierter Software. Mehr ist nicht möglich.

F) Zwischen Ring -2 und den anderen Ringen können nur noch definierte Signale ausgetauscht werden.

Ring -2 funktioniert wie Ring -1, mit einem Unterschied: Der RAM ist nicht verschlüsselt. D. h. zwischen Ring -1 und Ring -2 kannst Du keine Informationen mehr austauschen, da

Die CPU kann durch den Hypervisor weiterhin jederzeit in einen beliebigen Ring versetzt werden, aber die Informationen stammen dabei aus dem

Der "Ring -2" ist vom "Ring -1" in dem Sinn isoliert, dass man zwischen Ring -1 und Ring -2 nur noch ein sehr geringes Set an Signalen austauschen kann. Außerdem würde ich auf Hardwareebene einen verschlüsselten DMA einführen, mit folgender Eigenschaft:

Der DMA entschlüsselt den Quell-RAM und verschlüsselt ihn mit einem temporären Transport-Key, der aus dem verschlüsselten Quell-RAM stammt.

Alles, was der Hypervisor machen muss, wandert in den Ring -2, während der Ring -1, der derzeit unter der Kontrolle vom Hypervisor ist, unter die volle Kontrolle der VM kommt.

Jetzt brauche ich noch 2 Signale

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment