# Securing UnSafe Rust -> with <big>**XRust**</big> <!-- default rust buggy --> <!-- <img src="https://pad.binsky.org/uploads/454b9609-8eba-450f-a74b-962f7696cccc.png" style="border: none; background: none;" /> --> <!-- chily rust buggy --> <img src="https://pad.binsky.org/uploads/b5081feb-9292-44ff-826a-00abc14653c6.png" style="border: none; background: none;" /> <small style="color: var(--fbc-secondary-text);">Disclaimer: AchtungIchBinEigentlichTotalNeuInRustHabKeineAhnungUndWollteNurMalIrgendEinenCoolenVortragHalten!</small> Note: Nice to read zur Vorbereitung auf den Vortrag - https://medium.com/tempus-ex/making-unsafe-rust-safe-91d248821fa6 --- # Was war Rust noch gleich? <br> - Programmiersprache (von <img src="https://pad.binsky.org/uploads/2c17f612-49b4-48f0-9170-c8d918f1a492.png" style="border: none; width: 17%; margin: 0; padding: 0" />) - angekündigt im Mai 2015 in Version 1.0 - stark statisch typisiert - jedoch mit Typinferenz - „memory safety“ ohne einen „garbage collector“ Note: Typinferenz erlaubt, Typdeklarationen an solchen Stellen wegzulassen, an denen sie ohnehin eindeutig definiert sind Mit neuen Sprachmerkmalen wie Ownership, Borrowing und Lifetime garantiert Rust, dass ein Programm speichersicher ist, wenn es denn kompiliert werden kann. ---- # Speichersicherheit in Rust <br> | Verhindern von | mittels | | -------- | -------- | | - falschen Speicherzugriffen | - statischer Codeanalyse durch den Compiler | | - Pufferüberläufen | - standardmäßige Immutability | | - Race Conditions | - exklusiver Besitz von Speicherstellen | <small>(ungeordnete, nicht vollständige Liste)</small> Note: Rust versucht mit verschiedensten Mitteln, Fehler wie falsche Speicherzugriffe, Pufferüberläufe oder Race Conditions zu verhindern. Dabei spiel die statische Codeanalyse durch den Compiler eine große Rolle, aber auch Konzepte wie standardmäßige Immutability und exklusiver Besitz von Speicherstellen, was aber auch durch den Compiler geprüft wird. Die Konzepte im Detail vorzustellen würde jedoch den Rahmen sprengen. ---- <!-- .slide: data-background="https://pad.binsky.org/uploads/d96f9b01-db18-44e5-91a8-9e4428ef94fa.png" --> ## Also alles gut? Note: Wenn zur Compilezeit also schon die Speichersicherheit geprüft wird, kann ja eigentlich nichts mehr passieren, oder? --- <!-- .slide: data-background="https://pad.binsky.org/uploads/a6e825b8-b79e-4ab9-990f-ea8e658b2af1.png" --> ## UnSafe Rust <div style="width: 30%; margin-left: 35%;"> ``` rust unsafe { // die Welt muss brennen } ``` </div> Note: In Rust ist nämlich eine Art zweite Sprache versteckt, die die vom Compiler für "Save Rust" erzwungenen Speichersicherheitsgarantien nicht erzwingt. Das ist unsicheres Rust (unsafe Rust) und funktioniert genau wie das normale Rust, gibt aber zusätzliche - Zitat aus der Doku: Superkräfte. Mit unsafe Rust sagst du dem Compiler „Vertrau mir Bruder. Ich weiß, was ich tue.“ Davon abgesehen ist unsafe Rust aber tatsächlich notwendig, um z.B. Schlussendlich mit Hardware zu kommunizieren, was aber typ. die Hauptbibliotheken übernehmen. ---- ## Features von UnSafe Rust <br> - Dereferenzieren eines Rohzeigers - Aufrufen einer unsicheren Funktion oder Methode - Ändern einer veränderlichen statischen Variablen - Implementieren eines unsicheren trait - Zugreifen auf Felder in union Note: In unsicherem Rust können fünf Aktionen ausgeführt werden, die in sicherem Rust nicht ausgeführt werden können. Das heißt, diese 5 Aktionen werden vom Compiler nicht auf Speichersicherheit geprüft. Traits sind sowas wie ein Mix aus abstrakter Klasse und Interface, bieten also zusätzlich zu einem typischen Interface die Möglichkeit die Implementierung von Standard-Methoden zu hinterlegen. Und das ist der Moment in dem XRust ins Spiel kommt. --- # XRust ![](https://pad.binsky.org/uploads/b86b32b6-3503-40cc-8baa-1042bf53b26d.png) <!-- .element: class="fragment" data-fragment-index="1" --> Note: Securing UnSafe Rust Programs with XRust ist ein Paper, welches im Zuge der ICSE (International Conference on Software Engineering) 2020 veröffentlicht wurde. Darin wird analysiert, welche Probleme unsafe Rust mit sich bringt, und welche Methoden es gibt diese wieder zu lösen. XRust hat sich zum Ziel gesetzt dieses sog. unsafe Rust in einen sicheren Kontext zu verpacken. ---- ## Wieso nutzen Programmierer UnSafe Rust? <br> - Uneingeschränkte Speicherzugriffe <!-- .element: class="fragment" data-fragment-index="1" --> - ungeprüfte Typ- und Datenformat-Konvertierungen <!-- .element: class="fragment" data-fragment-index="2" --> - interne Zustände verändern / außer Kraft setzen <!-- .element: class="fragment" data-fragment-index="3" --> Note: Für das Paper wurde aus einer Stichprobe von std libs und crates analyiert wozu unsafe Rust primär verwendet wird. - Uneingeschränkte Speicherzugriffe - statt Objektreferenzen werden manchmal raw pointer mit unchecked Pointer-Arithmetik verwendet um auf ein Stück Speicher zuzugreifen - ungeprüfte Typ- und Datenformat-Konvertierungen - z.B. utf-8 zu utf-16 - low-level Operationen wie decoding von binary data oder serialization - interne Zustände verändern / außer Kraft setzen - ein internet Zustand ist z.B. die Länge eines Vektors, die von Rust automatisch erhöht wird, beim Pushen eines Elements - wenn also anders als gedacht auf Objekte zugegriffen wird, z.B. über raw pointer, müssen eben diese internen Zustände unter Umständen auch manuell angepasst werden, damit ein solches Objekt konsistent bleibt ---- ## Problem Safe und UnSafe Rust nutzen <span style="color: red">keine getrennten Speicherbereiche</span> ![](https://pad.binsky.org/uploads/b450f25b-4e01-4e87-88e1-4cc12bfdfae5.png) <!-- .element: class="fragment" data-fragment-index="1" --> Note: Das Problem ist, dass safe und unsafe Rust keine getrennten Bereiche sind, sondern durch unsafe Rust eben auch Speicher von safe Rust manipuliert werden kann. Damit könnten mittels unsafe Rust Daten aus safe Rust gelesen oder sogar manipuliert werden. Z.B. in dem ein Funktionspointer gehijacked wird. oder wie hier im Beispiel einfach mal ein Pointer um ein paar Stellen verschoben wird, um dann irgendwie Daten auszulesen. ---- ## Wie soll die Lösung aussehen? <br> - Räumliche / Abgegrenzte Speichersicherheit - Temporale Speichersicherheit Note: - Räumliche / Abgegrenzte Speichersicherheit - Temporale Speichersicherheit ---- ### Räumliche / Abgegrenzte Speichersicherheit <br> - Schutz vor Angriffen auf "Nicht-Kontrolldaten" - <span style="color: orange;">z.B. Objektzugriffe im UnSafe Code, die Daten außerhalb der unsicheren Region kompromittieren</span> <!-- .element: class="fragment" data-fragment-index="1" --> - Schutz vor Angriffen auf "Kontrolldaten" - <span style="color: orange;">z.B. vtable-Zeiger eines Trait-Objekts verschieben, um den Kontrollfluss zu bösartigem Code umzuleiten</span> <!-- .element: class="fragment" data-fragment-index="2" --> Note: Mit dem Verhindern von regionsübergreifenden Referenzen können Rust-Programme gegen folgendes geschützt werden: - Angriffe auf "Nicht-Kontrolldaten" (also Angriffe auf typ. Benutzerdaten) in unsicherem Rust-Code, die Objekte außerhalb der unsicheren Region beschädigen - Angriffe auf "Kontrolldaten" in unsicherem Rust-Code, die den vtable-Zeiger eines Trait-Objekts oder rohe Funktionszeiger außerhalb der unsicheren Region beschädigen, z.B. um den Kontrollfluss zu bösartigem Code umzuleiten ---- ### Temporale Speichersicherheit <br> - Schutz vor Angriffen durch ungeplante Reihenfolge von Speicherzugriffen - <span style="color: orange;">z.B. ein zuvor von UnSafe Rust verwendeter Speicherblock wird später vom Heap-Allocator an Safe Rust vergeben</span> ---- ## Lösung durch XRust <img src="https://pad.binsky.org/uploads/1b87f4e4-c5c9-4b80-8d2b-6f68744fd245.svg" style="width: 38%; border: none" /> -> angepasster Heap-Allocator <!-- .element: class="fragment" data-fragment-index="1" --> Note: In XRust ist der Heap logisch in zwei sich gegenseitig ausschließende Regionen unterteilt: eine unsichere Region und eine sichere Region. Dabei kann unsafe Rust Code nur auf Speicher der unsicheren Region zugreifen und somit den Speicher von sicherem Rust nicht korrumpieren. Speicherfehler in unsicherem Rust-Code werden also nicht verhindert, sondern nur Übergriffe in von Safe Rust verwendete Speicherbereiche. <hr> Die grundlegende Idee von XRust ist dabei ein neuartiger sog. Heap-Allocator. Der isoliert den Speicher von unsicherem Rust von dem, auf den nur in sicherem Rust zugegriffen wird. So soll im Grunde jegliche regionsübergreifende Speicherkorruption verhindert werden. ---- ## Ziel von XRust <br> ### <span style="color: #08ff00;">Integrität von Daten in sicherem Rust sicherstellen!</span> Note: Das Ziel von XRust ist es also nicht, Speichersicherheit in unsicheres Rust zu bringen, sondern die Integrität von Daten in sicherem Rust sicherzustellen. Dazu schauen wir uns an wie Heap Allocation so ganz grob funktioniert. --- # Heap-Allocation in Rust <br> - High-Level-Abstraktionen durch Kapselung von Heap-Operationen **Der Rust Compiler tut die Magic!** Note: Rust bietet High-Level-Abstraktionen für Heap-Speicher durch Kapselung von Heap-Operationen. Die Freigabe eines Heap-Objekts wird automatisch vom Rust-Compiler erledigt, wenn dessen Lifetime eben abgelaufen ist. Programmierer dürfen den Speicher nicht manuell freigeben, um Fehler wie doppelte Freigaben zu vermeiden. ---- ## ToDo: 2 unterschiedliche Heap Regionen! - zusätzliches "unsafe" Interface pro Allocation Funktion<!-- .element: class="fragment" data-fragment-index="1" --> - Compilererweiterung zum Handhaben unsicherer Heap-Region<!-- .element: class="fragment" data-fragment-index="2" --> - High-Level APIs für Zugriff auf "unsafe" Interfaces durch Standardbibliotheken<!-- .element: class="fragment" data-fragment-index="3" --> ``` - Vec::unsafe_with_capacity() zu Vec - unsafe_box() zu Box - unsafe_alloc() zu Alloc - ... ``` <!-- .element: class="fragment" data-fragment-index="4" --> Note: Wie bekommt man Unterstützung für 2 unterschiedlich Heap Regionen? - für jede Allocation Funktion wurde ein zusätzliches "unsafe" Interface hinzugefügt - der Compiler wurde erweitert, um Code zum Aufrufen dieser erweiterten Funktionen zum Handhaben der unsicheren Heap-Region zu generieren - und es brauchte neue High-Level APIs zum Zugriff auf die erweiterten Interfaces durch Standardbibliotheken --- ## Der Heap-Allocator von XRust <!-- #### Unsafe Regionen im Heap --> - Implementierung basiert auf ptmalloc2 - ... ---- ### ptmalloc2 - verwaltet pro Thread mehrere Bereiche mit separaten Heap-Segmenten - verschiedene Bereiche ermöglichen es Heap-Allocation/Deallocation gleichzeitig ohne Synchronisierung durchzuführen Note: ptmalloc2 verwaltet pro Thread mehrere Bereiche mit separaten Heap-Segmenten und Freilisten-Datenstrukturen. Das bietet eben den Vorteil, dass so Allocation Geschichten zwischen den Threads nicht synchronisiert werden müssen und damit theoretisch gleichzeitig stattfinden können. ---- ## Der Heap-Allocator von XRust <!-- #### Unsafe Regionen im Heap --> - Implementierung basiert auf ptmalloc2 - Unsichere Bereiche werden nicht als sichere Bereiche wiederverwendet - Bitmap zum Speichern des Typs der Heap-Segmente (safe/unsafe) Note: Zurück zum Allocator von XRust. - Die unsicheren Bereiche im Heap werden nicht wiederverwendet, um Objekte in sicheren Regionen zuzuweisen und umgekehrt - Um Laufzeitprobleme zu vermeiden wurde sich entschieden eine vorab zugewiesene Bitmap zu verwenden, um den Typ der Heap-Segmente (sicher oder unsicher) aufzuzeichnen Vorteil: Bitmap braucht relativ wenig zusätzlichen Speicher und kann schnell durch die Startadressen der Heap-Segmente indiziert werden ---- ## Regionsübergreifende Referenzen im Allocator <br> - Laufzeit-Checks zum Prüfen von (verbotenen) Referenzen Note: Dem Allocator wurden Laufzeit Checks hinzugefügt, um zu vermeiden, dass innerhalb einer unsafe Region Speicher von außerhalb referenziert wird. ---- Im Paper untersuchte Methoden zur prozessinternen Isolierung des Speichers - **memory guard pages** - **instrumentation** Note: Es wurden 2 prozessinterne Speicherisolationstechniken untersucht, um regionsübergreifende Speicherreferenzen zu erkennen bzw. zu verhindern. Bei der Verwendung von Memory Guard Pages wird die Isolation erzwungen, indem quasi unzugängliche Speicherseiten zwischen den beiden Regionen platziert werden. Das ist eine eher konservative Herangehensweise, weniger aufwändig, aber auch weniger sicher, falls man z.B. die Ziel-Speicheradresse kennt und nicht ausprobieren muss. Der Zugriff auf eine Guard Page erzeugt einen Segfault. Bei der Verwendung von Instrumentation werden hingegen Runtime Checks eingefügt, um regionsübergreifende Datenflüsse von der unsicheren Region in die sichere Region zu verhindern. Das ist der eher aufwändigere, aber aus meiner Perspektive auch viel spannendere Weg, dem wir jetzt noch etwas folgen werden. ---- ## Verhindern von regionsübergreifenden Referenzen mittels "instrumentation" <br> - Instrumentation für unsafe Rust Code und alle "unsafe Objekte" <!-- .element: class="fragment" data-fragment-index="1" --> - transitiv erreichbare Abhängigkeiten beachten <!-- .element: class="fragment" data-fragment-index="2" --> Note: - um die Seiteneffekte von unsafe Rust Code komplett zu isolieren, sollte die Instrumentation nicht nur für unsafe Rust Code durchgeführt werden, sondern auch für alle "unsafe Objekte". - Und zwar nicht nur Objekte, die direkt von unsafe Code berührt werden, sondern auch alle davon transitiv erreichbaren Daten. ---- ## Beispiel Was mit regionsübergreifenden Referenzen gemeint ist [Beispiel 1](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3b0ebda31a687ff4dac98bdd245adc41) [Beispiel 2](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5925b7816e6fa05917f6c795cb7051c6) [Beispiel 3](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=181bb0f702001e9b429170344904d772) Note: Beispiel 1: Out-of-bound read, da unchecked interne Zustände verändert wurden. Es wird die Länge verändert, der Buffer von v jedoch nicht vergrößert. XRust würde v somit in einer unsafe Region speichern. Beispiel 2: Index out of bounds exception. Diesen Fehler würde XRust nicht verhindern, da v durch den Zugriff in unsafe-Heap liegt und damit nicht die Konsistenz von Daten außerhalb gefährdet. Beispiel 3: auslesen eines geheimen Codes, der mittels XRust jedoch getrennt in der Save-Rust Heap Region liegen würde. XRust würde dies also wieder verhindern. ---- ## Beispiel 3 - Was XRust tun würde | | | | -------- | -------- | | ![](https://pad.binsky.org/uploads/15c95079-ab74-4a37-8355-ed45a1b38689.svg =400x) | ![](https://pad.binsky.org/uploads/2d0d77a8-bbd8-4bab-ab27-72f9ef57af62.svg =600x)<!-- .element: class="fragment" data-fragment-index="1" --> | --- # Evaluation - XRust mit Guard Pages -> Overhead von 0.15% im Median - XRust mit Instrumentation -> Overhead von 3.6% im Median - 5% geringere Performance des angepassten Heap-Allocator Note: XRust wurde mit 6 weltweit eingesetzten Anwendungen, sowie 5 Kernkomponenten der Rust Standardbibliotheken getestet. XRust mit Guard Pages erzeugt einen durchschnittlichen Overhead von 0.15% im Median (also ohne Ausreißer) und 2% im Durchschnitt. XRust mit Instrumentation erzeugt einen durchschnittlichen Overhead von 3.6% im Median (also ohne Ausreißer) und 21% im Durchschnitt. Bei dem Overhead geht es, wenn ich das richtig interpretiert habe um zusätzliche Rechenzeit. Die Performance des angepassten Heap-Allocator ist im Durchschnitt ca. 5% geringer, als mit ptmalloc2 Es gibt im Paper in Kapitel 6 noch eine genaue Aufschlüsselung welche Techniken in welchen der untersuchten Standardbibliotheken welchen Overhead haben, und wie dieser zu Stande kommt. ---- # Limitierungen - Einhaltung der Speichersicherheitsgarantie von Rust vorausgesetzt - XRust kann nicht mit dynamischer Codegenerierung umgehen Note: XRust geht davon aus, dass die Speichersicherheitsgarantie von Rust garantiert eingehalten wird, so dass keine Speicherfehler abseits on UnSafe Rust auftreten. Zudem kann XRust nicht mit dynamischer Codegenerierung umgehen, da neuer Code nicht statisch vom Compiler analysiert oder instrumentalisiert werden kann. --- # Zusammenfassung XRust soll <span style="color: orange;">sichere Speicherobjekte</span> in Rust davor zu <span style="color: orange;">schützen</span>, durch unsicheren Rust-Code beschädigt zu werden. <!-- .element: class="fragment" data-fragment-index="1" --> Idee: Adressraum eines Rust-Programms in <span style="color: orange;">zwei nicht überlappende Regionen</span> trennen <!-- .element: class="fragment" data-fragment-index="2" --> XRust <span style="color: orange;">verhindert Angriffe auf alle bekannten Rust-Schwachstellen</span> bei vernachlässigbarem Overhead <!-- .element: class="fragment" data-fragment-index="3" --> Note: XRust wurde im Paper als neuartiger Ansatz vorgestellt, um sichere Speicherobjekte in Rust davor zu schützen, durch unsicheren Rust-Code beschädigt zu werden. Die Schlüsselidee besteht darin, den Adressraum eines Rust-Programms in zwei nicht überlappende Regionen mit einem angepassten Heap-Allocator zu trennen und automatisch Laufzeitprüfungen einzufügen, um regionenübergreifende Verweise auf unsichere Objekte effizient zu erkennen. Die Autoren des Papers kommen, basierend auf umfangreichen, durchgeführten Bewertungen, zum Schluss, dass XRust äußerst effektiv und effizient ist. Es verhindert Angriffe auf alle bekannten Rust-Schwachstellen und weist dabei einen geringen oder vernachlässigbaren Overhead auf. --- ![](https://pad.binsky.org/uploads/487d2448-bec6-4048-a21a-69d87d60d907.png) ![](https://pad.binsky.org/uploads/f0c7183e-a43a-4d1e-8b66-f652c793afc9.png) <!-- .element: class="fragment" data-fragment-index="1" --> Note: An dieser Stelle möchte ich mich entschuldigen, denn eigentlich ist der Titel meines Vortrags auch ein bisschen Clickbait. XRust kann derzeit nämlich gar nicht eingesetzt werden. - Docker Container ist verlinkt, jedoch nicht mehr verfügbar - Building from Source ist beschrieben, jedoch gibt es da ein Problem - das angepasste ptmalloc2 welches als Submodule in dem Repo hinterlegt ist, lässt sich nicht aufrufen, da es hinter einer GitHub Enterprise Anmeldung versteckt ist. --- # Securing UnSafe Rust -> with <big>**XRust**</big> <!-- default rust buggy --> <!-- <img src="https://pad.binsky.org/uploads/454b9609-8eba-450f-a74b-962f7696cccc.png" style="border: none; background: none;" /> --> <!-- chily rust buggy --> <img src="https://pad.binsky.org/uploads/b5081feb-9292-44ff-826a-00abc14653c6.png" style="border: none; background: none;" /> Note: Das war meine kleine Veranschaulichung und Zusammenfassung des Papers über XRust --- ## Inhaltliche Quellen - https://dl.acm.org/doi/abs/10.1145/3377811.3380325 - https://doc.rust-lang.org/book <br><br> ### weitere Bilder - https://avatars.githubusercontent.com/u/14631425?s=400&v=4 - https://www.regenwurm-vlotho.de/wp-content/uploads/2018/06/regenwurm-welt-heile-welt.jpg
{"title":"Securing UnSafe Rust with XRust","type":"slide","slideOptions":{"transition":"slide","previewLinks":true,"width":"80%"}}