zurück zum Artikel

Linux 6.2 soll Performance trotz Retbleed-Patches zurückbringen

Oliver Müller

(Bild: heise online)

Der Linux-Kernel 6.2 setzt eine neue Retbleed-Strategie um, die Performance-Einbrüche bisheriger Ansätze beseitigt. Auch die Rust-Integration schreitet voran.

Linux 6.2 erblickte in der Nacht von Sonntag auf Montag dieser Woche das Licht der Welt. Die Rust-Integration verlässt die "Hello, world!"-Nische. BPF soll sich langfristig für Modul-Programmierung eigenen. Zudem legt der Kernel bei Sicherheitseinrichtungen nach.

Wie erwartet legte der Kernel eine zusätzliche Testwoche in Form eines achten Release-Kandidaten (rc8) ein. Das hatte aber diesmal keine technischen Gründe, wie das Beheben schwerwiegender, spät entdeckter Fehler. Vielmehr war die Extrarunde des Vorgängers indirekt Ursache für die Verzögerung. Der Auftakt für die Arbeiten an Linux 6.2 schob sich durch das späte Release von 6.1 [1] auf die Weihnachtsfesttage und Neujahr. Um trotz der urlaubsbedingt abwesenden Entwickler ausreichend Tests zu ermöglichen, hatte Torvalds bereits früh und wiederholt betont [2], dass er einen weiteren Release Candidate 8 einschieben wolle. Nach rc7 hielt er an seiner Aussage fest und die Extratestwoche kam. Unter "normalen" Umständen hätte Torvalds nach rc7 den finalen Linux 6.2 freigegeben.

Linux 5.19 führte Kernel-Patches gegen die Spectre-Variante Retbleed ein. Bereits im Release-Candiate rc7 sorgten diese für Performance-Probleme, sodass Nacharbeiten in Form eines zusätzlichen rc8 und damit eine weitere Testwoche nötig wurde [3]. Die Patches stützten sich auf die Intel-eigenen Hardware-basierte "Indirect Branch Restriction Speculation" (IBRS). IBRS funktioniert zwar, beschert aber auf Intel Skylake CPUs teils drastische Leistungseinbrüche [4]. Davon ist auch Linux seit 5.19 betroffen.

Linux 6.2 steuert nun gegen und bringt einen schnelleren Kniff für Skylake-basierte Systeme. Die Kernel-Boot-Option retbleed=stuff aktiviert eine rein softwarebasierte Lösung. Sie benötigt keine IBRS und "umschifft" diese.

Im Prozessor existiert ein spezieller Stapel, der "Return Stack Buffer" (RSB). Dieser nimmt Rücksprungadressen auf, die das System im Zuge seiner spekulativen Befehlsausführung als mögliche Rücksprungziele festhält. Dieser RSB kann nur 16 Adressen aufnehmen. Soll eine weitere auf den Stapel, geht die älteste Adresse verloren. Das ist dann der Fall, wenn die Aufrufketten länger werden – beim Linux-Kernel durchaus keine Seltenheit.

Beim "Rückarbeiten" eines solchen Stapels, dem ältere Adressen auf diese Weise verloren gingen, kommt es zwangsläufig zu einem Unterlauf (Underflow) des Stapels. Es fehlen Rücksprungadressen. Die Spekulationsmaschinerie der CPU stoppt an dieser Stelle jedoch nicht, sondern weicht auf den "Branch History Buffer" (BHB) aus. In diesem sind vorherige Sprünge protokolliert und dienen als Grundlage für neue Vorhersagen der spekulativen Befehlsausführung.

Dieser BHB kann gezielt durch falsche Rücksprünge "trainiert" werden, um so Schadcode ausführen zu lassen. Dieses "Training" findet im Userspace statt und schlägt auf den privilegierten Code im Kernel durch. Retbleed bringt den RBS zum Unterlauf, und schiebt dann eine falsch trainierte BHB unter.

IBRS entgegnet dem, indem im Userspace erlernte Sprungziele im Kernelspace verworfen werden. Im Kernelspace sorgt das für massiven Aufwand. Der neue Ansatz sorgt dafür, dass das Kernproblem – der Unterlauf der RBS – nicht stattfindet und damit die BHB gar nicht erst herangezogen wird. Eine "Schattenbuchhaltung" führt mittels eines Zählers Protokoll über die Tiefe des Stapels. Zudem wird der Aufrufstapel und auch die RSB mit Rücksprungzielen vorbelegt. Diese führen lediglich zu einem CPU-Befehl (INT3), der die Spekulation beendet, bevor die BHB zum Tragen kommt.

Auch das ist tendenziell im Kernel nicht billig. Diese Buchhaltungsfunktionen müssen entsprechenden Funktionsaufrufen voran- und nachgestellt werden. Auch sie kosten Taktzyklen, lassen aber hoffen, weniger Leistung zu schlucken als IBRS.

Linux 6.1 ebnete als erster Mainline-Kernel den Weg, Module auch in Rust zu programmieren. Mehr als ein "Hello, world!" war damit aber nicht möglich. Der neue Kernel legt hier wie vorgesehen nach und liefert notwendige Basiskonstrukte nach. Als Verbinder der C- und Rust-Welt ist das komplexe Makro #[vtable] wie erwartet eingeflossen. Das Rust-Makro hilft beim Erzeugen von C-structs aus Rust-traits. In einem trait kann Rust definierte, aber nicht implementierte Funktionen problemlos darstellen.

Der C-Code im Kernel nutzt in den betreffenden structs Nullzeiger, um eine Funktion als "nicht implementiert" zu markieren. Um nun aus einer Rust-trait eine C-struct zu erzeugen, muss der betreffende Code erkennen, wo Nullzeiger einzufügen sind. #[vtable] generiert für jede definierte Funktion aus einer trait ein spezielles Konstantenelement (constant member) in einer als trait dargestellten Tabelle. Diejenigen, die implementiert sind, erhalten den Wert true, die anderen false. Anhand dessen kann der Code, der die C-struct generiert, zur Compile-Zeit erkennen, was nicht implementiert ist und die Nullzeiger einfügen. Wichtig ist das beispielsweise für die C-struct file_operations, die eine Liste der von einem Treiber unterstützten Funktionen enthält.

Rust kennt zwei String-Typen str und String. Der erste Typ ist eine Referenz (borrow) auf einen String, wohingegen der zweite den String selbst enthält. Diese beiden bildet der Kernel-Rust-Support auf zwei Rust-Varianten CString und CStr ab, die die gleiche Funktion wie Strings in C übernehmen. Zudem gibt es einen speziellen Rust-Typ BStr, der Byte-Strings darstellt. Damit können mittels des Makros b_str!() Strings zu Byte-Zeichenketten konvertiert werden, die auch Nicht-ASCII-Zeichen enthalten.

Für Vektoren kommen neue Konstruktoren hinzu. Zudem kann Rust alle acht Log-Level des Kernel nutzen, da nun die notwendigen pr_*-Funktionen nachgeliefert wurden. Bislang waren nur zwei Log-Level mit pr_info!() und pr_emerg!() nutzbar. Auch liefert der Kernel alle Error-Codes aus errno-base.h und Rust-trait-Implementierungen für den Error-Typ nach. Neue Makros für Asserts und Debugging runden den neuen Rust-Satz ab.

Eine Übersicht über die Änderungen an der Rust-Integration findet sich im entsprechenden Commit des Entwicklers Miguel Ojeda [5]. Die Rust-Einbindung legt damit deutlich zu und verlässt die Demo-Ecke von "Hello, world!". Bis die ersten praxistauglichen Module in Rust im Kernel erscheinen, wird es jedoch noch etwas dauern. Für den nächsten Kernel 6.3 stehen bereits die nächsten Erweiterungen als Pull-Request [6] bereit, die neben neuen Typen auch Nacharbeiten etwa an alloc und somit an Elementarem umfassen.

BPF (Berkeley Packet Filter) erlaubt mit den "user defined object"-BPF-Programmen eigene Objekte und Objekthierarchien zu erzeugen. Außerdem können BPF-Programme mithilfe der "Basic Building Blocks" des BPF-Runtime eigene Datenstrukturen flexibel anlegen und verlinkte Listen nutzen. BPF mausert sich damit langsam zur vollwertigen Programmiersprache. Erklärtes Ziel ist es [7], in beschränkter Form Kernel-Programmierung in BPF C zuzulassen.

Seit Version 2.5 kennt der Kernel den Synchronsationsmechanismus RCU (Read, Copy, Update). Das Eintreten (lock) und Verlassen (unlock) eines kritischen Lese-Blocks können nun auch die in Kernel 5.10 eingeführten "sleepable" BPF-Programme [8] nutzen.

Des Weiteren haben BPF-Programme erweiterten Zugriff auf control-group local storage. Auch können sie auf task_struct-Objekte zugreifen und diese speichern.

Auf ARM64 gibt es zwei Sicherheitsmechanismen gegen Stack-Buffer-Overflow-basierte Angriffe. Die auf allen ARM64-Systemen nutzbaren Shadow-Stacks schützen vor dem Überschreiben von Rücksprungadressen, indem sie die Rücksprungadressen des aktiven Stapels und einer parallel geführten Schattenkopie abgleichen und so Änderungen auf die Schliche kommen. Auf Systemen ab ARM 8.3 mit aktiver Pointer-Authentication werden Rücksprungadressen kryptografisch signiert und sind somit bereits gegen Modifikationen abgesichert [9]. Ein Shadow-Stack bringt auf diesen Systemen keinen Sicherheitsgewinn und frisst nur unnötig Taktzyklen.

Linux 6.2 erlaubt nun, Shadow-Stacks zur Boot-Zeit ein- und auszuschalten. Bislang war das Aktivieren der Shadow-Stacks nur zur Compile-Zeit des Kernels möglich. Die Folge waren separate Kernel-Builds für Pointer-Authentication ohne Shadow-Stacks und ohne Pointer-Authentication, jedoch mit Shadow-Stacks. Jetzt kann der gleiche Kernel auf Systemen mit und ohne Pointer-Authentication zum Einsatz kommen, da sich die Shadow-Stacks zur Laufzeit aktivieren lassen.

Für Intel-Prozessoren spendiert der neue Kernel FineIBT, das "Fine-grained Indirect Branch Tracking". Es ist ein alternativer Ansatz für "Control Flow Integrity" (CFI). CFI ist allgemein eine Technik, um durch Angriffe initiierte Abweichungen vom Programmfluss zu erkennen. FineIBT setzt auf der hardware-basierten "Control Enforcement Technology" (CET) von Intel auf. Durch die Hardware-Unterstützung reduzieren sich der sonst übliche Overhead gegenüber rein software-basierte CFI. Intern arbeitet es auf dem Prinzip, auf der aufrufenden Seite einer Funktion einen Hash-Wert zu setzen, den die aufgerufene Seite überprüft. Stimmen die Werte nicht überein, lässt sich so ein Angriff erkennen.

Intels "asynchronous exit notification"-Mechanismus unterstützt Linux 6.2 ebenfalls. Über ihn lassen sich Single-Step-Attacken in SGX-Enclaven [10] erkennen.

Nachdem 2019 der Floppy-Treiber im Kernel bereits als "verwaist" markiert wurde und sich doch noch ein neuer Maintainer fand, wartet der Floppy-Code nun mit einigen Bugfixes und Änderungen auf. In Linux 5.11 hatte sich ein Memory-Leak in den Kernel eingeschlichen. Der Bug macht Teile des Arbeitsspeichers unansprechbar, wenn auf eine defekte Diskette zugegriffen oder diese zu früh aus dem Laufwerk entfernt wird.

Freuen dürfte das vor allem Nutzer in Ländern, in denen die Diskette noch relevantes Medium ist. Japan beispielsweise schreibt noch gesetzlich beim Austausch mit bestimmten Behörden die Floppy als Medium vor. Aber auch Anwender in Industrie, Luftfahrt und Medizin nutzen noch Disketten. Auch wenn die letzte Boeing 747 im Dezember letzten Jahres von Band lief, ist die Maschine noch im Einsatz und erhält Navigationsupdates per 3,5"-Diskette. Das Gleiche gilt für industrielle Produktionsanlagen, Laborsysteme oder Medizintechnik, die für Jahrzehnte ausgelegt sind und nicht auf Emulatoren umgerüstet werden können.

Revolutionäre technische Neuerungen bietet Linux 6.2 nicht. Der Ausbau von Rust war bereits angekündigt und ist konsequent und plangemäß in den Kernel eingeflossen. Auch wenn es noch ein längerer Weg zur praxistauglichen Rust-Integration sein mag, bleiben die Entwickler in Bewegung und liefern wie angekündigt stetig Erweiterungen.

Der Ausbau von BPF ist spannend: Das Ziel, Kernel-Funktionen in BPF zu entwickeln, kann alteingesessene Kernel-Entwickler ein wenig schauern lassen. Die Helper-Funktionen im Userspace und das mächtige BPF-Arsenal im Kernelspace können zum Spekulieren über die Zukunft der Kernels verleiten. Da mag ein bisschen Microkernel-Flair in Linux Einzug halten. Auch wenn das ein neues Licht auf den Disput zwischen Linus Torvalds und Andrew Tanenbaum aus dem Jahr 1992 werfen mag.

Positiv dürfte insbesondere Administratoren mit älterer Hardware die überarbeitete Retbleed-Gegenmaßnahme auf Skylake-Prozessoren stimmen. Die ersten Tests lassen hoffen, dass die massiven Performance-Einbrüche damit nicht mehr auftreten. Wie groß der Leistungsverlust beim neuen Ansatz in der Praxis sein wird, wird sich in den nächsten Wochen zeigen.

Ansonsten feilen die Entwickler in Linux 6.2 an Sicherheits-Features. Sie greifen neue Ansätze auf. Damit lässt sich das Release als Wartungs-Release mit Sicherheitsfokus und Vorarbeiten für zukünftige Weichenstellungen – siehe BPF-Aufbau und Rust-Integration – auffassen.

Der neue Kernel lädt wie gewohnt auf kernel.org zum Herunterladen ein. Alle Änderungen finden sich gesammelt im ausführlichen Kernel-Changelog [11].

(dmk [12])


URL dieses Artikels:
https://www.heise.de/-7521579

Links in diesem Artikel:
[1] https://www.heise.de/news/Linux-6-1-als-naechster-Langzeit-Kernel-erschienen-7393140.html
[2] https://lore.kernel.org/all/CAHk-=whSVeeQN9vO-WSxFkNs0zbUJEBqND-1VO8OJtmu_sn_nw@mail.gmail.com/
[3] https://www.heise.de/news/Linux-5-19-mit-weniger-Altlasten-und-neuer-CPU-Architektur-7204768.html
[4] https://www.heise.de/news/Bis-zu-70-Prozent-langsamer-Kernel-Fix-gegen-Retbleed-macht-Linux-VMs-lahm-7261222.html
[5] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=96f42635684739cb563aa48d92d0d16b8dc9bda8
[6] https://lore.kernel.org/lkml/20230212183249.162376-1-ojeda@kernel.org/
[7] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=db6bf999544c
[8] https://www.heise.de/news/Long-Term-Release-Linux-5-10-Die-wichtigsten-Neuerungen-im-Ueberblick-4996971.html
[9] https://www.heise.de/news/Linux-5-14-mit-geheimem-Speicher-und-sicherem-Hyperthreading-6179070.html
[10] https://www.heise.de/news/Linux-5-11-Support-fuer-Intel-SGX-neue-Treiber-und-kleinere-Verbesserungen-5051869.html
[11] https://cdn.kernel.org/pub/linux/kernel/v6.x/ChangeLog-6.2
[12] mailto:dmk@heise.de