
CVE-2017-13868: Informationsleck uninitialisierter Kernel-Heap-Daten in XNU.
Die Funktion ctl_ctloutput unter macOS High Sierra 10.13 ignoriert den Rückgabewert eines Aufrufs von sooptcopyin, wodurch ein Race-Fenster entsteht, um nicht initialisierte Kernel-Heap-Daten an den Benutzerbereich preiszugeben. ctl_ctloutput-leak ist ein Proof-of-Concept-Exploit, der versucht, diesen Informationsleck auszulösen. Für die Ausnutzung sind Root-Rechte erforderlich.
Dieser Exploit wurde erfolgreich unter macOS High Sierra 10.13.1 Beta 17B25c und iOS 10.1.1 14B100 (unter mach_portal) getestet.
Hier ist der relevante Teil von ctl_ctloutput unter macOS High Sierra 10.13:
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data is allocated
M_WAITOK); // without M_ZERO.
if (data == NULL)
return (ENOMEM);
/*
* 4108337 - copy user data in case the
* kernel control needs it
*/
error = sooptcopyin(sopt, data, // (b) sooptcopyin() is
sopt->sopt_valsize, sopt->sopt_valsize); // called to fill the
} // buffer; the return
len = sopt->sopt_valsize; // value is ignored.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) The getsockopt()
kcb->userdata, sopt->sopt_name, // implementation is
data, &len); // called to process
if (data != NULL && len > sopt->sopt_valsize) // the buffer.
panic_plain("ctl_ctloutput: ctl %s returned "
"len (%lu) > sopt_valsize (%lu)\n",
kcb->kctl->name, len,
sopt->sopt_valsize);
socket_lock(so, 0);
if (error == 0) {
if (data != NULL)
error = sooptcopyout(sopt, data, len); // (d) If (c) succeeded,
else // then the data buffer
sopt->sopt_valsize = len; // is copied out to
} // userspace.
Dieser Code führt Folgendes aus:
getsockopt allokiert, ohne das Flag M_ZERO zu setzen, um die allokierten Bytes zu nullen.getsockopt-Daten aus dem Benutzerbereich mittels sooptcopyin in den gerade allokierten Datenpuffer. Diese Kopieraktion soll die allokierten Daten vollständig überschreiben, weshalb das Flag M_ZERO nicht benötigt wurde. Allerdings wird der Rückgabewert von sooptcopyin nicht überprüft, was bedeutet, dass das Kopieren fehlschlagen kann und nicht initialisierte Daten im Puffer zurückbleiben. Das Kopieren könnte fehlschlagen, wenn das Programm beispielsweise eine nicht abgebildete Adresse an getsockopt übergibt.getsockopt-Implementierung für diesen Kernel-Steuerungssocket auf. Diese Implementierung sollte den Eingabepuffer verarbeiten, möglicherweise modifizieren und verkürzen, und einen Ergebnis-Code zurückgeben. Die Implementierung darf jedoch davon ausgehen, dass der bereitgestellte Puffer bereits initialisiert ist (da er theoretisch aus dem Benutzerbereich stammt), und daher ändern mehrere Implementierungen den Puffer gar nicht. Die NECP-Funktion necp_ctl_getopt gibt beispielsweise einfach 0 zurück, ohne den Datenpuffer zu verarbeiten.Indem wir also eine nicht abgebildete Datenadresse an getsockopt übergeben, können wir einen Heap-Puffer kontrollierter Größe allokieren, verhindern, dass der Inhalt dieses Puffers initialisiert wird, und dann einen Aufruf von sooptcopyout erreichen, der versucht, diesen Puffer zurück an die nicht abgebildete Adresse zu schreiben. Damit das Kopieren gelingt, müssen wir diese Adresse zwischen den Aufrufen von sooptcopyin und sooptcopyout neu abbilden. Wenn uns das gelingt, geben wir nicht initialisierte Kernel-Heap-Daten an den Benutzerbereich preis.
Es stellt sich heraus, dass dieses Rennen recht einfach zu gewinnen ist. Bei Tests auf meinem MacBook Pro von 2015 lag die durchschnittliche Anzahl der Versuche, das Rennen zu gewinnen, nie über 600, und der Median lag nie über 5. Unter iOS 10.1.1 auf einem iPhone 7 war das Rennen sogar noch einfacher zu gewinnen, typischerweise mit nicht mehr als 2 Versuchen. (Diese Tests wurden mit deaktiviertem DEBUG durchgeführt, da die printfs den Exploit drastisch verlangsamen.)
Zum Bauen führen Sie make aus. Siehe die Spitze der Makefile für verschiedene Bauoptionen.
Führen Sie den Exploit aus, indem Sie die Ziel-Leak-Größe in der Befehlszeile angeben:
$ sudo ./ctl_ctloutput-leak 128
000000: ef be ad de ef be ad de 00 00 00 00 00 00 00 00
000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000020: 00 00 00 00 00 00 00 00 01 00 00 00 40 80 00 00
000030: de 28 45 00 04 00 00 00 a0 ff 4a 26 80 ff ff ff
000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000050: 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000070: 00 00 00 00 00 00 00 00 ef be ad de ef be ad de
Ich habe dieses Problem am 7. Oktober 2017 an Apple gemeldet. Es wurde die CVE‑ID CVE-2017-13868 zugewiesen. Apple behob die Probleme in macOS 10.13.2 und iOS 11.2.
Der ctl_ctloutput-leak-Code wird in die Public Domain entlassen. Als Höflichkeit bitte ich darum, dass Sie, falls Sie auf diesen Code verweisen oder ihn verwenden, mich als Urheber nennen.
ctl_ctloutput sooptcopyout auf, um den Datenpuffer zurück in den Benutzerbereich zu kopieren, falls die eigentliche getsockopt-Implementierung keinen Fehler zurückgibt.