Bug im Linux-Kernel: Keine Panik!
Es gibt einige Gründe für Admins, ein Kernel-Update durchzuführen. Meist handelt es sich um Sicherheitsupdates, manchmal verspricht ein neuer Kernel auch aktuelle Treiber, die mehr leisten als der Vorgänger. So oder so gilt: Der Vorgang ist eine Standardprozedur, und Admins erwarten, dass alles funktioniert. Entsprechend unruhig werden sie, wenn etwas danebengeht. Wenn zum Beispiel wegen eines Fehlers einzelne – womöglich zentrale – Bauteile des Systems den Dienst verweigern.
Ist der Fehler in einem wichtigen Teil des Kernels, etwa in der Speicherverwaltung, lässt sich unter Umständen das System gezielt zum Absturz bringen. Selbst ein Fehler in einer exotischen Komponente, die nur auf wenigen Systemen aktiv ist, kann so zum Sicherheitsproblem werden: Durch sie lässt sich womöglich eine große Zahl an Systemen gezielt angreifen. Dieser Text berichtet von der Suche nach einem solchen Bug, die sich über mehrere Monate hinzog und nur durch das Zusammenspiel mehrerer Entwickler erfolgreich war.
Die Ausgangssituation
Das Setting ist schnell erklärt: Mehrere aktuelle, sehr leistungsfähige HP-Maschinen (DL380 Gen. 9) und Ubuntu 14.04 bilden die Basis. Openstack betreibt auf ihnen virtuelle Maschinen, im Hintergrund wird die verteilte Speicherlösung Quobyte eingesetzt. Sie ist auf allen beteiligten Hosts als Server wie als Client aktiv. Die Server-Komponente macht aus den einzelnen Festplatten der einzelnen Hosts einen großen, logischen Datenspeicher, der per zentraler Schnittstelle ansprechbar ist. Obendrein steuert Quobyte für die Daten des Netzwerkspeichers automatisch Redundanz bei. Per Treiber für Fuse (Filesystem in Userland) binden die Hosts das Dateisystem aus der Ferne ein. Die virtuellen Festplatten der Openstack-Systeme liegen auf jenem Fuse-Mount.
Weil die genannten HP-Server viel aktuelle Hardware nutzen, fiel schon anfangs die Wahl auf die LTS-Kernel (LTS steht für Long Term Support) von Canonical. Anders als Red Hat und SUSE bietet Canonical einen Weg, um auch auf Systemen mit Langzeit-Unterstützung aktuelle Kernels zu verwenden.
Der Deal ist simpel: Canonical portiert die Kernel der aktuellen Releases auf die jeweils letzte LTS-Version zurück. Für Ubuntu 14.04, die bei Erscheinen dieses Artikels aktuelle LTS-Version von Ubuntu, stehen etwa die Kernels von Ubuntu 14.10, 15.04 und 15.10 als LTS-Kernel zur Verfügung. Beim Erscheinen der nächsten LTS-Version von Ubuntu, also 16.04, fängt das Spiel von vorne an. Für 16.04 wird es dann die Kernel späterer Ubuntu-Versionen geben und für Ubuntu 14.04 wird wenigstens der Kernel von Ubuntu 16.04 als Backport zur Verfügung stehen. Der Support für ältere Kernel auf Ubuntu 14.04 läuft allerdings bald nach der Veröffentlichung von 16.04 aus. Wer sich für die LTS-Kernels auf Ubuntu 14.04 entscheidet, muss zumindest dann zwangsläufig ein Update einspielen.
Insgesamt erscheint die Lösung deutlich eleganter als die anderer Hersteller, die Kernel-Version auf dem Stand von vor mehreren Jahren zu belassen und den Kernel selbst mit schier unendlich vielen Patches aufzupolstern. Beim Erscheinen des LTS-Kernel 4.2 im Februar 2016 fiel die Entscheidung, auf diesen zu setzen. Denn viele Updates versprachen bessere Performance auf mehreren Ebenen des Systems.
Das Problem
Doch mit dieser Entscheidung begann die Misere: Der frische Kernel 4.2 bootete zwar ohne Murren und erlaubte auch den Login per SSH. Sobald Openstack jedoch virtuelle Maschinen auf dem System starten wollte, verabschiedete sich der Kernel mit einer hässlichen Panic-Meldung. Das Problem war zuverlässig zu reproduzieren, lediglich der Stack-Trace wies Unterschiede zwischen den einzelnen Crashes auf. Als Stack-Trace bezeichnet der Kernel eine Liste aufgerufener Funktionen kurz vor dem Absturz: Mit dieser Information ist es leichter, die Programmfunktion im Kernel zu finden, die den Absturz auslöst.
Noch jemand mit diesem Problem?
Der erste Schritt auf der Suche nach einer Lösung war die Suche nach Leidensgenossen. Im Fedora-Bugtracker fand sich tatsächlich ein interessanter Fehlerbericht(öffnet im neuen Fenster) : Die dort beschriebenen Umstände entsprachen etwa denen der lokalen Umgebung, in welcher das Problem ebenfalls auftrat.
Wenig Hoffnung gab die dokumentierte Aktivität des Bugreports: Antworten der Fedora- oder Red-Hat-Entwickler gab es nicht. Letztlich blieb nur, einen entsprechenden Kommentar zu hinterlassen und damit zu dokumentieren, dass man das Problem ebenfalls hatte.
Beim Hersteller melden
Weil das Problem akut nur auf Ubuntu zu reproduzieren war, war der nächste logische Schritt, einen Bug-Report in Launchpad anzulegen(öffnet im neuen Fenster) , dem Ubuntu-Bug-Tracker. Um einen aussagekräftigen Fehlerbericht zu schreiben, war allerdings eine weitergehende Analyse des Problems angesagt. Schnell war klar, dass das Problem in irgendeiner Weise mit der Speicherverwaltung des Linux-Kernels zu tun haben musste: Die Funktion "kmem_cache_alloc" tauchte zwar nicht in jedem Stack-Trace auf, aber doch in den meisten. Auffällig war außerdem, dass der eigentliche Crash fast immer in "fuse_direct_io" auftrat, einer Funktion, die ebenfalls Speicher im Kernel für sich beansprucht.
Damit gab es eine erste Arbeitshypothese: Aus bisher unbekanntem Grund ging bei der Verwendung von Arbeitsspeicher im Fuse-Treiber des Linux-Kernels etwas schief. Programmfunktionen von Fuse wollten deshalb auf Speicherbereiche zugreifen, die ihnen nicht gehörten. Der Rest war hinlänglich dokumentiertes Standard-Verhalten: Der Linux-Kernel stirbt in solchen Fällen den Panic-Tod und nötigt den Admin zum Reboot.
Es war also klar, dass es mit Linux 4.2 vorerst nichts werden würde. Bis auf einen Server durchliefen alle Systeme ein Downgrade auf Linux 3.19.
Hilfe von Fuse?
Weiter ging die Suche nach Hilfe bei den Fuse-Entwicklern: Direkt auf der Projektmailingliste besteht die Möglichkeit, Fehler zu melden und nach Ideen für mögliche Patches zu fragen. Auf Basis der Resultate des Debuggings für Launchpad entstand so auch ein Bericht an die Fuse-Mailingliste. Leider erwies sich Fuse nicht als das aktivste Projekt der FL/OSS-Szene: Von wenigen Rückfragen abgesehen kam nicht viel hilfreiches Feedback. Am Ende lief die Diskussion darauf hinaus, dass der Fuse-Treiber von Quobyte wohl das Problem sei.
Das war allerdings auszuschließen: Die ganze Idee hinter Fuse besteht darin, dass Nutzer – auch ohne Rechte des Systemadministrators "root" – eigene Dateisysteme anlegen und nutzen. Selbst wenn ein Fuse-Treiber also einen Bug hätte, dürfte dieser niemals zum Absturz des Host-Systems führen. Richtig war hingegen die Anmerkung der Fuse-Entwickler, dass sie in der Lage sein müssen, den Fehler zu reproduzieren. Sonst sei ordentliches Debugging unmöglich – die Entwickler konnten sich schließlich schlecht selbst ein Openstack aufbauen, um unter gleichen Bedingungen zu testen.
Schwer zu reproduzieren
Das Thema Reproduzierbarkeit war komplexer als zunächst vermutet: Zuverlässig trat das Problem eingangs nur dann auf, wenn Openstack auf KVM-VMs zugreifen wollte, deren virtuelle Festplatten auf dem Fuse-Mount lagen. Zwar war das Openstack-Produkt noch in der Beta-Phase. Dennoch war es keine Option, wahllos virtuelle Maschinen der Beta-Kunden abstürzen zu lassen, um das Problem auf Host-Ebene zu debuggen. Aus diesem Grund war die einzige Maschine mit 4.2 längst aus dem Openstack-Verbund ausgeschieden. Aber außerhalb von Openstack war der Fehler zunächst nicht reproduzierbar: Alle Versuche, direkt auf dem Fuse-Mount ohne Openstack und KVM den Fehler auszulösen, schlugen fehl.
Immerhin war damit der nächste Debugging-Schritt klar: Aufgabe war nun, ein Setup zu bauen, das möglichst klein war und mit Standard-Tools von Ubuntu auskam. Gleichzeitig musste sich das Problem mit diesem Mini-Setup aber auch reproduzieren lassen. Weil der Fuse-Treiber für Quobyte offenbar in der Lage war, das Fehlverhalten auszulösen, war Quobyte selbst der nächste Ansprechpartner.
Quobyte liefert
Mit dem Problem konfrontiert waren auch die Quobyte-Entwickler erst einmal ratlos. Der erste Debugging-Ansatz lief auf das Reproduzieren des Problems mit einem anderen Fuse-Treiber als dem eigenen hinaus: NTFS-3G war das Mittel der Wahl. Nach etlichen Tagen des Debuggings dann der Durchbruch: Auch mit einer aktuellen Version von NTFS-3G trat das Problem auf – wenn der NTFS-3G-Treiber gegen die Version 3 der Fuse-Bibliothek gelinkt ist. Langsam ließen sich Details ausmachen: Der Absturz passierte nur bei asynchronem, direktem I/O-Zugriff (asynchronous direct I/O). Dieser ist erst in Version 3 der Fuse-Bibliothek verfügbar. Die Version 2, die bei allen Distributionen zum Einsatz kommt, unterstützt diese Funktion nicht.
Quobyte war es damit gelungen, eine generische Basis zu schaffen, auf der sich das Problem reproduzieren ließ. Und es war auch klar, dass der Bug sich im Fuse-Modul des Linux-Kernels befindet. Dass es ab hier noch mehr als vier Monate dauern würde, bis der kaputte Code identifiziert sein würde, ahnte zu dieser Zeit noch niemand.
Kernel-Changelogs wälzen
Der nächste Schritt auf der To-Do-Liste war, die Kernel-Version ausfindig zu machen, die als erste nachweislich das Problem hatte. Linux 3.19 arbeitete tadellos, Linux 4.2 nicht mehr. Zwischen den beiden Kernels lagen nur zwei weitere Versionen: 4.0 und 4.1. Linux 4.0 funktionierte wie Version 3.19 wunderbar, Linux 4.1 zeigte das gleiche Verhalten wie der Kernel der Version 4.2. Ein Blick in das Git-Verzeichnis des Linux-Kernels offenbarte, dass es zwischen Linux 4.1 und 4.2 tatsächlich Änderungen am Fuse-Modul des Kernels gegeben hat(öffnet im neuen Fenster) . In jener Änderung musste also der Grund zu finden sein, der Fuse ab Linux 4.2 reproduzierbar zum Absturz brachte. Linux-Urgestein Christoph Hellwig selbst hatte den Patch eingebracht, der ohne Probleme in den Kernel-Code durchgewunken wurde und dort seit Februar 2015 integriert war.
Wieder ergriff Quobyte die Initiative: In mühevoller und wochenlanger Arbeit fanden die Entwickler dort heraus, dass tatsächlich ein Teil des genannten Patches problematisch ist. Denn unter Umständen greift das Fuse-Modul durch eine falsche Reihenfolge von Funktionsaufrufen auf Speicher zu, den es zuvor bereits wieder freigegeben hat.
Problem betrifft nicht nur einige wenige Nutzer
Hilfe bekamen die Quobyte-Leute vom Kernel-Team bei Canonical. Es ließ Quobyte an der eigenen Expertise beim Debuggen von Kernel-Problemen teilhaben und erkannte selbst auf Basis der Quobyte-Arbeit erst im Lauf des Vorgangs die Tragweite des Bugs. War man anfangs noch der Überzeugung, man müsse das Problem gar nicht für Ubuntu 16.04 lösen, weil es die meisten Nutzer nicht betreffe, geht man mittlerweile davon aus, dass der Fix für 16.04 notwendig ist. Denn ein Kernel, der sich möglicherweise auch durch Anwender gezielt zum Absturz bringen lässt, ist eben auch ein Sicherheitsrisiko – und zumindest bei Systemen mit aktiviertem Fuse ist genau das der Fall.
Mittlerweile arbeiten die Ubuntu-Entwickler sogar an einem Backport des Patches für den Kernel der aktuellen Ubuntu-Version 15.10. Das ist ein klares Zeichen, dass die Tragweite des Problems deutlich größer ist als anfangs angenommen. Über diesen Umweg wird letztlich auch das eingangs erwähnte Setup einen korrigierten Kernel 4.2 erhalten.
Das Resultat: zehn Zeilen Patch
Die Identifikation des defekten Codes durch Quobyte war de facto der entscheidende Schritt auf dem Weg zu einem Patch. Quobyte-Entwickler Robert Döbbelin stellte diesen auf der Fuse-Mailingliste zur Diskussion(öffnet im neuen Fenster) , nachdem Seth Forshee vom Kernel-Team bei Canonical sein Sanctus gegeben hatte. In Form eines Test-Pakets war zu diesem Zeitpunkt bereits ein Kernel in Version 4.2 verfügbar, der das beschriebene Fehlverhalten nicht mehr zeigte. Bis der Patch in Ubuntu als Teil eines neuen Kernel-Pakets ankommt, dürfte noch eine Weile vergehen. Zwar steht es den Linux-Anbietern frei, auch abseits des Hauptzweigs des Linux-Kernels Patches in ihre Kernel-Pakete einzubauen. Doch für solche Fälle gibt es diverse interne Prozesse, die zu befolgen sind. Immerhin: Auch auf die Kernel-Mailingliste hat es der Patch mittlerweile geschafft, so dass zur Aufnahme in den offiziellen Linux-Kernel nur noch Linux-Chef Linus Torvalds seine Zustimmung geben muss.
Dass der gesamte Patch letztlich nur zehn Zeilen lang ist und effektiv nur einen Funktionsaufruf von einer Stelle an eine andere verschiebt, trägt zu seiner Akzeptanz allerdings bei.
Lessons learned
Die große Lehre aus dieser Erfahrung ist, dass Kernel-Debugging nur funktioniert, wenn die Entwickler ein Problem selbst sehen und genau untersuchen können. Der erste Schritt auf dem Weg zum Debugging von Linux ist insofern das Bauen einer möglichst reduzierten Testumgebung: Nur wenn die Bedingungen klar sind, in denen ein Problem auftritt, lassen sich gute Rückschlüsse auf mögliche Lösungen ziehen.
Über den Autor
Martin Gerhard Loschwitz arbeitet bei SysEleven in Berlin. Er beschäftigt sich dort bevorzugt mit den Themen Openstack, Software Defined Networking und Software-defined Storage.
- Anzeige Hier geht es zum Handbuch für Softwareentwickler bei Amazon Wenn Sie auf diesen Link klicken und darüber einkaufen, erhält Golem eine kleine Provision. Dies ändert nichts am Preis der Artikel.



