
CVE-2020-25578 und CVE-2020-25579: Einige FreeBSD Info-Leak-Bugs, die ich 2020 gefunden habe.
ufs_create zu auditieren und 0 Fehler findenstruct dirent-Objekten, die auf dem Stack allokiert werden, durchsickernmsdosfs_readdir unvollständig ist. Sie haben eine Instanz des Fehlers gepatcht, aber nicht eine zweite.mqueuefs, autofs, smbfs und tmpfs finden, die es mir erlauben, einen vollen 8-Byte-Zeiger zu leaken. PoC zur Bestätigung schreiben.Wie oben erwähnt, war der ursprüngliche Fehler, den ich fand, in msdosfs_readdir, während ich den Patch für den oben verlinkten Commit analysierte.
Der grundlegende Ablauf für den Aufruf von readdir auf FreeBSD ist wie folgt:
#include <dirent.h>
int main(void) {
struct dirent *dp;
DIR *dirp;
dirp = opendir("./somedir");
dp = readdir(dirp);
}
Abhängig vom Dateisystem, in dem sich somedir befindet, könnte jede der vielen *_readdir-Funktionen im FreeBSD-Kernel aufgerufen werden.
Der obige Patch fügt eine Funktion namens dirent_terminate hinzu, die aufgerufen werden soll, bevor ein struct dirent-Objekt an den Userspace zurückgegeben wird (oft mit der Funktion uiomove). Diese Funktion setzt die Padding-Bytes sowie alle verbleibenden Bytes im d_name-Feld der Struktur auf Null. Die Definition von struct dirent ist hier.
Wenn wir uns den Patch ansehen, in Zeile 1562 sehen wir, dass dirent_terminate mit der Variable dirbuf als Argument aufgerufen wird. Danach wird uiomove aufgerufen, um den Inhalt von dirbuf zurück in den Userspace zu kopieren. Beachten Sie jedoch, dass diese Codezeilen innerhalb des Blocks dieser if-Anweisung liegen. Der Kommentar über dieser if-Anweisung erklärt, dass dieser Zweig nur genommen wird, wenn readdir auf dem Root des MSDOS-Dateisystems aufgerufen wird. Daher können wir diese if-Anweisung einfach überspringen, indem wir readdir in einem Unterverzeichnis jenseits des Root des Dateisystems aufrufen.
Weiter unten sehen wir einen weiteren Aufruf von uiomove in Zeile 1691. Wenn wir den Code jedoch genau lesen, stellen wir fest, dass dirent_terminate in diesem Fall nicht aufgerufen wird, was bedeutet, dass die Padding-Bytes nicht initialisiert bleiben. Leider wurde das d_name-Feld zu Beginn dieser Funktion hier auf Null gesetzt, sodass wir keinen größeren Leak erhalten können.
Zuerst hatte ich keinen USB-Stick, also musste ich einen Weg finden, ein MSDOS-Dateisystem zu mounten. Folgendes funktioniert:
$ dd if=/dev/zero of=test.img bs=512 count=256000
$ sudo mdconfig -a -t vnode -f test.img
$ sudo newfs_msdos -s 131072000 /dev/md1 # My mdconfig returned md1
$ mkdir ./temp
$ sudo mount -t msdosfs /dev/md1 ./temp
$ mkdir ./temp/test_dir
Das PoC befindet sich in original_poc.c. Einfach mit clang kompilieren und aus demselben Verzeichnis wie die obigen Befehle ausführen, und Sie werden die durchgesickerten Bytes ausgegeben sehen.
Ich begann, nach Varianten davon zu suchen. Ich glaube, ich habe einfach nach uiomove\(&.*, gegrept, was etwa 15-20 Ergebnisse lieferte, und habe sie alle von Hand überprüft. Leider existiert keine der Varianten standardmäßig in FreeBSD (die Dateisysteme müssen manuell aktiviert / in den Kernel kompiliert werden). Die Funktionen mit den Varianten sind wie folgt:
mqfs_readdirtmpfs_dir_getdotdenttmpfs_dir_getdotdotdentsmbfs_readvdirautofs_readdir_oneDer Fehler ist in all diesen Funktionen genau derselbe, daher werde ich nur mqfs_readdir behandeln.
struct dirent entry auf dem Stack allokiertdirent_terminate aufgerufen, um die Padding- und d_name-Felder der Struktur auf Null zu setzenvfs_read_dirent aufgerufen. Diese Funktion ruft uiomove auf, um die Struktur in den Userspace zu kopierenBisher sieht alles gut aus, oder? Nicht unbedingt. Wir müssen sicherstellen, dass alle Felder der Struktur initialisiert sind. Wenn Sie den Code genau betrachten, sehen Sie, dass das Feld d_off nicht initialisiert bleibt. Der Typ dieses Feldes ist off_t, was im Wesentlichen ein int64_t ist. Wenn die Struktur in den Userspace kopiert wird, erhalten wir in diesem Feld nicht initialisierte Daten.
Dieses selbe PoC wird für alle Varianten funktionieren, Sie müssen es nur auf einem anderen Dateisystem ausführen. Für mqueuefs gehen Sie wie folgt vor (benötigt, dass mqueuefs zuerst aktiviert / in den Kernel kompiliert wurde):
$ mkdir ./temp
$ sudo mount -t mqueuefs null ./temp
Das PoC selbst befindet sich in variants_poc.c. Einfach mit clang kompilieren und aus demselben Verzeichnis wie die obigen Befehle ausführen. Sie werden Kernel-Zeiger ausgegeben sehen (vermutlich ein Stack-Zeiger und ein Codebereichs-/Heap-Zeiger, ich habe nicht nachgeprüft).