Linux 5.10: Kommende Kernel-Version bricht mit fast 30-jährigem Erbe

Seite 2: Kleiner Exkurs in den Kernel-Code

Inhaltsverzeichnis

Um die Sicherheitsrelevanz von set_fs() besser zu verstehen, lohnt ein Blick auf eines von hunderten im Kernel-Code vorhandenen Anwendungsszenarien.

Der folgende Code stammt aus der Funktion kernel_readv() aus Linux 5.9.3 (fs/splice.c ab Zeile 352). Verwendet wird kernel_readv() im splice()-Systemcall, der zum performanten Kopieren von Daten zwischen einer Datei und einer Pipe dient. Normalerweise wäre ein "Umweg" über den Userspace notwendig, um aus einem File-Handle zu lesen und in ein anderes hineinzuschreiben. splice() delegiert hingegen den gesamten Kopiervorgang an den Kernel.

01 old_fs = get_fs();
02 set_fs(KERNEL_DS);
03 /* The cast to a user pointer is valid due to the set_fs() */
04 res = vfs_readv(file, (const struct iovec __user *)vec, vlen, &pos, 0);
05 set_fs(old_fs);

In der ersten Zeile sichert die Funktion den aktuellen Inhalt von FS. In der zweiten Zeile setzt sie FS auf den Kernelspace (KERNEL_DS = Kernel-Datensegment). Anschließend ruft sie die "normale" Dateisystemroutine vfs_readv() auf, die normalerweise auf Userspace-Puffer beschränkt wäre. Zu guter Letzt schreibt sie den gesicherten Inhalt von FS über set_fs() wieder zurück: FS zeigt wieder auf den Userspace. Der Kommentar in der dritten Zeile fasst den Vorgang recht gut zusammen.

Für den Zeitraum des Aufrufs von vfs_readv() verschiebt sich die in der Variable addr_limit definierte Userspace-Grenze ein Stück in den Kernelspace. Eben hier liegt das potenzielle Sicherheitsrisiko: vfs_readv() kann Daten dank set_fs() temporär in den Kernelspace-Pipe-Buffer schreiben – ohne Zugriffsverletzung und daraus resultierendem Abbruch.

Die beschriebene Vorgehensweise wurde über Jahrzehnte so praktiziert und blieb als Sicherheitsproblem unbemerkt. 2010 jedoch wurde das Gefahrenpotenzial offensichtlich: Basierend auf der von Nelson Elhage entdeckten Sicherheitslücke CVE-2010-4258 zeigte Dan Rosenberg einen Exploit zur Rechteausweitung (Privilege Escalation) zu root im Kontext der bereits erwähnten Kernelfunktion access_ok().

Das Problem ist, dass zwischen den beiden (atomaren) set_fs()-Aufrufen unvorhergesehene Dinge passieren können. Kommt es hier zu einem schwerwiegenden Fehler in Form eines so genannten Oops, unterbleibt das Zurückschreiben des old_fs-Werts. Teile des Kernelspace-Datensegments bleiben somit für den Zugriff aus dem Userspace offen.

Rosenbergs Exploit zeigt, dass sich ein solcher Fehler bewusst provozieren lässt. Aber auch versehentlich herbeigeführte Probleme mit set_fs() sind bekannt: Ende 2016 wurde bei einem Touchscreen-Treiber von LG unter Android in einem möglichen Ablaufpfad das zurücksichernde set_fs() gar nicht aufgerufen. Die Folge: Der Treiber kehrte in gewissen Situationen mit dem offenen Kernelspace in den Userspace zurück.