Was ist eine Retpoline und wie funktioniert sie?


244

Um gegen Kernel oder prozessübergreifende Speicher Offenlegung (der zu mildern Specter Angriff), Kernel der Linux - 1 wird mit einer neuen Option kompiliert werden , -mindirect-branch=thunk-externeingeführt , um gccindirekte Gespräche über eine sogenannte auszuführen retpoline .

Dies scheint ein neu erfundener Begriff zu sein, da eine Google-Suche erst in jüngster Zeit verwendet wird (im Allgemeinen alle im Jahr 2018).

Was ist eine Retpoline und wie verhindert sie die jüngsten Angriffe auf die Offenlegung von Kernelinformationen?


1 Es ist jedoch nicht Linux-spezifisch - ein ähnliches oder identisches Konstrukt scheint als Teil der Minderungsstrategien auf anderen Betriebssystemen verwendet zu werden.


6
Ein interessanter Support-Artikel von Google.
sgbj

2
oh, so ist es ausgesprochen / ˌtræmpəˈlin / (amerikanisch) oder / ˈtræmpəˌliːn / (britisch)
Walter Tross

2
Sie könnten erwähnen, dass dies der Linux- Kernel ist, obwohl dies in diese Richtung gccweist! Ich habe lkml.org/lkml/2018/1/3/780 nicht wie auf der Linux Kernel Mailing List-Site erkannt, auch nicht, als ich dort nachgesehen habe (und mir wurde ein Schnappschuss zugestellt, da er offline war).
PJTraill

@PJTraill - fügte ein Linux-Kernel-Tag hinzu
RichVel

@PJTraill - guter Punkt, ich habe den Fragentext aktualisiert. Beachten Sie, dass ich es aufgrund seines relativ offenen Entwicklungsprozesses zuerst im Linux-Kernel gesehen habe, aber zweifellos werden dieselben oder ähnliche Techniken als Abschwächung für das gesamte Spektrum von Open- und Closed-Source-Betriebssystemen verwendet. Ich sehe das also nicht als Linux-spezifisch an, aber der Link ist es sicherlich.
BeeOnRope

Antworten:


158

Der von sgbj in den Kommentaren von Paul Turner von Google erwähnte Artikel erklärt Folgendes viel ausführlicher, aber ich werde es versuchen:

Soweit ich dies aus den derzeit begrenzten Informationen zusammensetzen kann, ist eine Retpoline ein Rücklauftrampolin , das eine Endlosschleife verwendet, die niemals ausgeführt wird, um zu verhindern, dass die CPU über das Ziel eines indirekten Sprungs spekuliert.

Der grundlegende Ansatz ist in Andi Kleens Kernel-Zweig zu sehen, der sich mit diesem Problem befasst:

Es führt den neuen __x86.indirect_thunkAufruf ein, der das Aufrufziel lädt, dessen Speicheradresse (die ich aufrufen werde ADDR) oben auf dem Stapel gespeichert ist, und führt den Sprung mithilfe der RETAnweisung aus. Der Thunk selbst wird dann mit dem Makro NOSPEC_JMP / CALL aufgerufen, mit dem viele (wenn nicht alle) indirekte Aufrufe und Sprünge ersetzt wurden. Das Makro platziert einfach das Aufrufziel auf dem Stapel und stellt die Rücksprungadresse bei Bedarf korrekt ein (beachten Sie den nichtlinearen Kontrollfluss):

.macro NOSPEC_CALL target
    jmp     1221f            /* jumps to the end of the macro */
1222:
    push    \target          /* pushes ADDR to the stack */
    jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
    call    1222b            /* pushes the return address to the stack */
.endm

Die Platzierung von callam Ende ist erforderlich, damit der Kontrollfluss nach Beendigung des indirekten Aufrufs hinter der Verwendung des NOSPEC_CALLMakros fortgesetzt wird , sodass er anstelle eines regulären Makros verwendet werden kanncall

Der Thunk selbst sieht wie folgt aus:

    call retpoline_call_target
2:
    lfence /* stop speculation */
    jmp 2b
retpoline_call_target:
    lea 8(%rsp), %rsp 
    ret

Der Kontrollfluss kann hier etwas verwirrend sein, lassen Sie mich Folgendes klarstellen:

  • call schiebt den aktuellen Anweisungszeiger (Beschriftung 2) auf den Stapel.
  • leaFügt dem Stapelzeiger 8 hinzu und verwirft effektiv das zuletzt gepusste Quadword, das die letzte Rücksprungadresse ist (zu Bezeichnung 2). Danach zeigt die Oberseite des Stapels wieder auf die reale Rücksprungadresse ADDR.
  • retspringt zum *ADDRStapelzeiger und setzt ihn auf den Anfang des Aufrufstapels zurück.

Am Ende ist dieses ganze Verhalten praktisch gleichbedeutend mit direktem Springen *ADDR. Der einzige Vorteil, den wir erhalten, besteht darin, dass der für return-Anweisungen verwendete Verzweigungsprädiktor (Return Stack Buffer, RSB) bei der Ausführung des callBefehls davon ausgeht, dass die entsprechende retAnweisung zum Label 2 springt.

Der Teil nach dem Label 2 wird tatsächlich nie ausgeführt, es ist einfach eine Endlosschleife, die theoretisch die Anweisungspipeline mit JMPAnweisungen füllen würde . Durch die Verwendung von LFENCE, PAUSEoder allgemeiner eine Anweisung verursacht die Befehlspipeline Strömungsabriß sein stoppt die CPU verschwendet keine Energie und Zeit auf dieser spekulativen Ausführung. Dies liegt daran, dass für den Fall, dass der Aufruf von retpoline_call_target normal zurückkehren LFENCEwürde, dies die nächste auszuführende Anweisung wäre. Dies ist auch das, was der Verzweigungsprädiktor basierend auf der ursprünglichen Rücksprungadresse (dem Etikett 2) vorhersagt.

Um aus Intels Architekturhandbuch zu zitieren:

Anweisungen, die einem LFENCE folgen, können vor dem LFENCE aus dem Speicher abgerufen werden, werden jedoch erst ausgeführt, wenn der LFENCE abgeschlossen ist.

Beachten Sie jedoch, dass in der Spezifikation niemals erwähnt wird, dass LFENCE und PAUSE die Pipeline zum Stillstand bringen. Daher lese ich hier ein wenig zwischen den Zeilen.

Zurück zu Ihrer ursprünglichen Frage: Die Offenlegung von Kernelspeicherinformationen ist aufgrund der Kombination zweier Ideen möglich:

  • Obwohl die spekulative Ausführung nebenwirkungsfrei sein sollte, wenn die Spekulation falsch war, wirkt sich die spekulative Ausführung immer noch auf die Cache-Hierarchie aus . Dies bedeutet, dass bei einer spekulativen Ausführung eines Speicherladevorgangs möglicherweise immer noch eine Cache-Zeile entfernt wurde. Diese Änderung in der Cache-Hierarchie kann identifiziert werden, indem die Zugriffszeit auf den Speicher, der demselben Cache-Satz zugeordnet ist, sorgfältig gemessen wird.
    Sie können sogar einige Bits beliebigen Speichers verlieren, wenn die Quelladresse des gelesenen Speichers selbst aus dem Kernelspeicher gelesen wurde.

  • Der indirekte Verzweigungsprädiktor von Intel-CPUs verwendet nur die untersten 12 Bits des Quellbefehls, so dass es einfach ist, alle 2 ^ 12 möglichen Vorhersageverläufe mit benutzergesteuerten Speicheradressen zu vergiften. Diese können dann, wenn der indirekte Sprung innerhalb des Kernels vorhergesagt wird, spekulativ mit Kernel-Berechtigungen ausgeführt werden. Mit dem Cache-Timing-Seitenkanal können Sie somit beliebigen Kernelspeicher verlieren.

UPDATE: Auf der Kernel-Mailingliste gibt es eine laufende Diskussion, die mich zu der Annahme führt, dass Retpolinen die Probleme mit der Verzweigungsvorhersage nicht vollständig mindern, da neuere Intel-Architekturen (Skylake +) zurückfallen, wenn der Return Stack Buffer (RSB) leer ist an den anfälligen Branch Target Buffer (BTB):

Retpoline als Minderungsstrategie tauscht indirekte Zweige gegen Renditen aus, um zu vermeiden, dass Vorhersagen verwendet werden, die vom BTB stammen, da sie von einem Angreifer vergiftet werden können. Das Problem bei Skylake + ist, dass ein RSB-Unterlauf auf die Verwendung einer BTB-Vorhersage zurückgreift, die es dem Angreifer ermöglicht, die Kontrolle über Spekulationen zu übernehmen.


Ich denke nicht, dass die LFENCE-Anweisung wichtig ist. Die Implementierung von Google verwendet stattdessen eine PAUSE-Anweisung. support.google.com/faqs/answer/7625886 Beachten Sie, dass in der von Ihnen zitierten Dokumentation angegeben wird, dass "nicht ausgeführt wird", nicht "wird nicht spekulativ ausgeführt".
Ross Ridge

1
Auf dieser Google-FAQ-Seite: "Die Pausenanweisungen in unseren obigen Spekulationsschleifen sind für die Richtigkeit nicht erforderlich. Dies bedeutet jedoch, dass die unproduktive spekulative Ausführung weniger Funktionseinheiten auf dem Prozessor belegt." Es stützt also nicht Ihre Schlussfolgerung, dass LFENCE hier der Schlüssel ist.
Ross Ridge

@ RossRidge Ich stimme teilweise zu, für mich sieht dies nach zwei möglichen Implementierungen einer Endlosschleife aus, die die CPU andeuten, den Code nach PAUSE / LFENCE nicht spekulativ auszuführen. Wenn der LFENCE jedoch spekulativ ausgeführt und nicht zurückgesetzt wurde, weil die Spekulation korrekt war, würde dies der Behauptung widersprechen, dass er erst ausgeführt wird, wenn die Speicherladevorgänge abgeschlossen sind. (Andernfalls müsste der gesamte Satz von Anweisungen, die spekulativ ausgeführt wurden, zurückgesetzt und erneut ausgeführt werden, um die Spezifikationen zu erfüllen.)
Tobias Ribizel

1
Dies hat den Vorteil , push/ retdass es nicht Unwucht der Rückkehr-Adresse Prädiktor - Stack. Es gibt eine falsche Vorhersage (gehen Sie zu, lfencebevor die tatsächliche Absenderadresse verwendet wird), aber die Verwendung einer call+ -Modifikation rspgleicht dies aus ret.
Peter Cordes

1
oops, Vorteil gegenüber push / ret(in meinem letzten Kommentar). re: your edit: RSB-Unterlauf sollte unmöglich sein, da die Retpoline a enthält call. Wenn die Kernel-Prävention dort einen Kontextwechsel durchführen würde, würden wir die Ausführung mit dem RSB fortsetzen, der vom callin den Scheduler vorbereitet wurde . Aber vielleicht könnte ein Interrupt-Handler mit genug rets enden, um den RSB zu leeren.
Peter Cordes

46

Eine Retpoline soll vor dem Exploit der Verzweigungszielinjektion ( CVE-2017-5715 ) schützen . Dies ist ein Angriff, bei dem ein indirekter Verzweigungsbefehl im Kernel verwendet wird, um die spekulative Ausführung eines beliebigen Codeabschnitts zu erzwingen. Der ausgewählte Code ist ein "Gadget", das für Angreifer irgendwie nützlich ist. Zum Beispiel kann Code so gewählt werden, dass Kerneldaten durch die Auswirkungen auf den Cache verloren gehen. Die Retpoline verhindert diesen Exploit, indem sie einfach alle indirekten Verzweigungsbefehle durch einen Rückgabeanweis ersetzt.

Ich denke, der Schlüssel zur Retpoline ist nur der "ret" -Teil, der die indirekte Verzweigung durch eine Rückgabeanweisung ersetzt, sodass die CPU den Rückgabestapel-Prädiktor anstelle des ausnutzbaren Verzweigungsprädiktors verwendet. Wenn stattdessen ein einfacher Push- und ein Return-Befehl verwendet würden, wäre der Code, der spekulativ ausgeführt würde, der Code, zu dem die Funktion schließlich zurückkehren würde, und kein für den Angreifer nützliches Gadget. Der Hauptvorteil des Trampolinteils scheint darin zu bestehen, den Rückgabestapel beizubehalten. Wenn die Funktion tatsächlich zu ihrem Aufrufer zurückkehrt, wird dies korrekt vorhergesagt.

Die Grundidee hinter der Verzweigungszielinjektion ist einfach. Es nutzt die Tatsache aus, dass die CPU nicht die vollständige Adresse der Quelle und des Ziels von Zweigen in ihren Zweigzielpuffern aufzeichnet. So kann der Angreifer den Puffer mit Sprüngen in seinem eigenen Adressraum füllen, die zu Vorhersage-Treffern führen, wenn ein bestimmter indirekter Sprung im Kernel-Adressraum ausgeführt wird.

Beachten Sie, dass retpoline die direkte Offenlegung von Kernelinformationen nicht verhindert, sondern nur verhindert, dass indirekte Verzweigungsbefehle verwendet werden, um ein Gadget spekulativ auszuführen, das Informationen offenlegt. Wenn der Angreifer andere Mittel findet, um das Gadget spekulativ auszuführen, verhindert die Retpoline den Angriff nicht.

Das Papier Spectre Attacks: Ausnutzung der spekulativen Ausführung durch Paul Kocher, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, Michael Schwarz und Yuval Yarom geben folgenden Überblick darüber, wie indirekte Zweige genutzt werden können ::

Indirekte Zweige ausnutzen. Bei dieser Methode wählt der Angreifer aus der ROP (Return Oriented Programming) ein Gadget ausaus dem Adressraum des Opfers und beeinflusst das Opfer, das Gadget spekulativ auszuführen. Im Gegensatz zu ROP ist der Angreifer nicht auf eine Sicherheitsanfälligkeit im Opfercode angewiesen. Stattdessen trainiert der Angreifer den Branch Target Buffer (BTB), um eine Verzweigung von einem indirekten Verzweigungsbefehl zur Adresse des Gadgets falsch vorherzusagen, was zu einer spekulativen Ausführung des Gadgets führt. Während die spekulativ ausgeführten Anweisungen aufgegeben werden, werden ihre Auswirkungen auf den Cache nicht zurückgesetzt. Diese Effekte können vom Gadget verwendet werden, um vertrauliche Informationen zu verlieren. Wir zeigen, wie mit einer sorgfältigen Auswahl eines Gadgets mit dieser Methode ein beliebiger Speicher des Opfers gelesen werden kann.

Um das BTB zu missbrauchen, findet der Angreifer die virtuelle Adresse des Gadgets im Adressraum des Opfers und führt dann indirekte Verzweigungen zu dieser Adresse durch. Dieses Training wird über den Adressraum des Angreifers durchgeführt, und es spielt keine Rolle, was sich an der Gadget-Adresse im Adressraum des Angreifers befindet. Alles, was erforderlich ist, ist, dass der Zweig, der zum Trainieren von Zweigen verwendet wird, dieselbe virtuelle Zieladresse verwendet. (Solange der Angreifer Ausnahmen behandelt, kann der Angriff auch dann funktionieren, wenn im Adressraum des Angreifers kein Code an der virtuellen Adresse des Gadgets zugeordnet ist.) Es ist auch keine vollständige Übereinstimmung der Quelladresse erforderlich des für die Ausbildung verwendeten Zweigs und die Adresse des Zielzweigs. Somit hat der Angreifer eine erhebliche Flexibilität bei der Einrichtung des Trainings.

Ein Blogeintrag mit dem Titel Lesen von privilegiertem Speicher mit einem Seitenkanal durch das Project Zero-Team bei Google bietet ein weiteres Beispiel dafür, wie die Injektion von Zweigzielen verwendet werden kann, um einen funktionierenden Exploit zu erstellen.


9

Diese Frage wurde vor einiger Zeit gestellt und verdient eine neuere Antwort.

Zusammenfassung :

"Retpoline" -Sequenzen sind ein Softwarekonstrukt, mit dem indirekte Verzweigungen von der spekulativen Ausführung isoliert werden können. Dies kann angewendet werden, um sensible Binärdateien (z. B. Betriebssystem- oder Hypervisor-Implementierungen) vor Angriffen auf die Injektion von Zweigzielen gegen ihre indirekten Zweige zu schützen.

Das Wort " Ret Poline " ist ein Portmanteau der Wörter "Return" und "Trampolin", ähnlich wie die Verbesserung " Rel Poline " aus "Relative Call" und "Trampolin" geprägt wurde. Es handelt sich um ein Trampolin-Konstrukt, das unter Verwendung von Rückgabeoperationen konstruiert wurde und auch im übertragenen Sinne sicherstellt, dass jede damit verbundene spekulative Ausführung endlos "abprallt".

Um die Offenlegung des Kernels oder des prozessübergreifenden Speichers (Spectre-Angriff) zu verhindern, wird der Linux-Kernel [1] mit einer neuen Option kompiliert, -mindirect-branch=thunk-externdie in gcc eingeführt wird, um indirekte Aufrufe über eine sogenannte Retpoline auszuführen.

[1] Es ist jedoch nicht Linux-spezifisch - ein ähnliches oder identisches Konstrukt scheint als Teil der Schadensbegrenzungsstrategien auf anderen Betriebssystemen verwendet zu werden.

Die Verwendung dieser Compileroption schützt nur in betroffenen Prozessoren vor Spectre V2 , für die das für CVE-2017-5715 erforderliche Mikrocode-Update erforderlich ist. Es funktioniert mit jedem Code (nicht nur mit einem Kernel), aber nur Code mit "Geheimnissen" ist einen Angriff wert.

Dies scheint ein neu erfundener Begriff zu sein, da eine Google-Suche erst in jüngster Zeit verwendet wird (im Allgemeinen alle im Jahr 2018).

Der LLVM-Compiler hat -mretpolineseit dem 4. Januar 2018 einen Wechsel . An diesem Datum wurde die Sicherheitsanfälligkeit erstmals öffentlich gemeldet . GCC stellte ihre Patches am 7. Januar 2018 zur Verfügung.

Das CVE-Datum deutet darauf hin, dass die Sicherheitsanfälligkeit im Jahr 2017 " entdeckt " wurde, betrifft jedoch einige der in den letzten zwei Jahrzehnten hergestellten Prozessoren (daher wurde sie wahrscheinlich vor langer Zeit entdeckt).

Was ist eine Retpoline und wie verhindert sie die jüngsten Angriffe auf die Offenlegung von Kernelinformationen?

Zunächst einige Definitionen:

  • Trampolin - Manchmal als indirekte Sprungvektoren bezeichnet Trampoline sind Speicherstellen, die Adressen enthalten, die auf Interrupt-Serviceroutinen, E / A-Routinen usw. verweisen. Die Ausführung springt in das Trampolin und springt dann sofort heraus oder springt, daher der Begriff Trampolin. GCC unterstützt traditionell verschachtelte Funktionen, indem zur Laufzeit ein ausführbares Trampolin erstellt wird, wenn die Adresse einer verschachtelten Funktion verwendet wird. Dies ist ein kleiner Code, der sich normalerweise auf dem Stapel im Stapelrahmen der enthaltenen Funktion befindet. Das Trampolin lädt das statische Kettenregister und springt dann zur realen Adresse der verschachtelten Funktion.

  • Thunk - Ein Thunk ist eine Unterroutine, mit der eine zusätzliche Berechnung in eine andere Unterroutine eingefügt wird. Thunks werden hauptsächlich verwendet, um eine Berechnung zu verzögern, bis das Ergebnis benötigt wird, oder um Operationen am Anfang oder Ende der anderen Unterroutine einzufügen

  • Memoization - Eine Memoized-Funktion "merkt" sich die Ergebnisse, die bestimmten spezifischen Eingaben entsprechen. Nachfolgende Aufrufe mit gespeicherten Eingaben geben das gespeicherte Ergebnis zurück, anstatt es neu zu berechnen, wodurch die primären Kosten eines Aufrufs mit bestimmten Parametern von allen außer dem ersten Aufruf der Funktion mit diesen Parametern eliminiert werden.

Sehr grob gesagt ist ein Retpolin ein Trampolin mit einer Rückkehr als Thunk , um die Memoisierung im indirekten Zweigprädiktor zu " verderben " .

Quelle : Die Retpoline enthält einen PAUSE-Befehl für Intel, für AMD ist jedoch ein LFENCE-Befehl erforderlich, da der PAUSE-Befehl auf diesem Prozessor kein Serialisierungsbefehl ist, sodass die pause / jmp-Schleife überschüssige Leistung verbraucht, da über das Warten auf die Rückkehr spekuliert wird das richtige Ziel falsch vorhersagen.

Arstechnica hat eine einfache Erklärung des Problems:

"Jeder Prozessor hat ein Architekturverhalten (das dokumentierte Verhalten, das beschreibt, wie die Anweisungen funktionieren und von dem Programmierer abhängig sind, um ihre Programme zu schreiben) und ein mikroarchitektonisches Verhalten (das Verhalten einer tatsächlichen Implementierung der Architektur). Diese können auf subtile Weise voneinander abweichen. Beispielsweise wartet ein Programm, das einen Wert von einer bestimmten Adresse im Speicher lädt, architektonisch, bis die Adresse bekannt ist, bevor es versucht, das Laden durchzuführen. Mikroarchitekturell versucht der Prozessor jedoch möglicherweise, die Adresse spekulativ zu erraten, damit sie gestartet werden kann Laden des Werts aus dem Speicher (der langsam ist), noch bevor absolut sicher ist, welche Adresse verwendet werden soll.

Wenn der Prozessor falsch vermutet, ignoriert er den erratenen Wert und führt das Laden erneut durch, diesmal mit der richtigen Adresse. Das architektonisch definierte Verhalten bleibt somit erhalten. Diese fehlerhafte Vermutung stört jedoch andere Teile des Prozessors - insbesondere den Inhalt des Caches. Diese mikroarchitektonischen Störungen können erkannt und gemessen werden, indem festgelegt wird, wie lange es dauert, auf Daten zuzugreifen, die sich im Cache befinden sollten (oder nicht), sodass ein Schadprogramm Rückschlüsse auf die im Speicher gespeicherten Werte ziehen kann. "

Aus Intels Artikel : " Retpoline: A Branch Target Injection Mitigation " ( .PDF ):

"Eine Retpoline-Sequenz verhindert, dass die spekulative Ausführung des Prozessors den" indirekten Verzweigungsprädiktor "(eine Möglichkeit zur Vorhersage des Programmflusses) verwendet, um an eine Adresse zu spekulieren, die von einem Exploit gesteuert wird (erfüllt Element 4 der fünf Elemente der Verzweigungszielinjektion (Spectre-Variante 2) ) die oben aufgeführte Zusammensetzung ausnutzen). "

Hinweis: Element 4 lautet: "Der Exploit muss diesen indirekten Zweig erfolgreich beeinflussen, um ein Gadget spekulativ falsch vorherzusagen und auszuführen. Dieses vom Exploit ausgewählte Gadget verliert die geheimen Daten über einen Seitenkanal, typischerweise durch Cache-Timing."

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.