
CVE-2017-13868: Divulgazione di informazioni su dati non inizializzati dell'heap del kernel in XNU.
La funzione ctl_ctloutput in macOS High Sierra 10.13 ignora il valore di ritorno di una chiamata a
sooptcopyin, il che apre una finestra di race condition per divulgare dati non inizializzati dell'heap del kernel allo spazio utente.
ctl_ctloutput-leak è una prova di concetto che tenta di innescare questa fuga di informazioni.
Lo sfruttamento richiede privilegi di root.
Questo exploit è stato confermato funzionante su macOS High Sierra 10.13.1 Beta 17B25c e iOS 10.1.1 14B100 (sotto mach_portal).
Ecco la parte rilevante di ctl_ctloutput su macOS High Sierra 10.13:
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data viene allocato
M_WAITOK); // senza M_ZERO.
if (data == NULL)
return (ENOMEM);
/*
* 4108337 - copia dei dati utente nel caso in cui il
* controllo del kernel ne abbia bisogno
*/
error = sooptcopyin(sopt, data, // (b) sooptcopyin() viene
sopt->sopt_valsize, sopt->sopt_valsize); // chiamata per riempire
} // il buffer; il valore di
len = sopt->sopt_valsize; // ritorno viene ignorato.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) L'implementazione di
kcb->userdata, sopt->sopt_name, // getsockopt() viene
data, &len); // chiamata per elaborare
if (data != NULL && len > sopt->sopt_valsize) // il 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) Se (c) ha avuto
else // successo, il buffer
sopt->sopt_valsize = len; // dati viene copiato
} // nello spazio utente.
Questo codice fa quanto segue:
data di getsockopt, senza specificare il
flag M_ZERO per azzerare i byte allocati.getsockopt dallo spazio utente usando sooptcopyin, riempiendo il buffer dati
appena allocato. Questo copyin dovrebbe sovrascrivere completamente i dati allocati, motivo per cui
il flag M_ZERO non era necessario. Tuttavia, il valore di ritorno di sooptcopyin non viene
controllato, il che significa che è possibile che il copyin sia fallito, lasciando dati non
inizializzati nel buffer.
Il copyin potrebbe fallire se, ad esempio, il programma passasse un indirizzo non mappato a getsockopt.getsockopt per questo socket di controllo del kernel. Questa
implementazione dovrebbe elaborare il buffer di ingresso, eventualmente modificandolo e accorciandolo, e
restituire un codice di risultato. Tuttavia, l'implementazione è libera di presupporre che il buffer fornito sia già
stato inizializzato (poiché in teoria proviene dallo spazio utente), e quindi diverse
implementazioni non modificano affatto il buffer. La funzione NECP
necp_ctl_getopt, ad esempio, restituisce semplicemente 0 senza elaborare
affatto il buffer dati.getsockopt non restituisce un errore, ctl_ctloutput chiama
sooptcopyout per copiare il buffer dati di nuovo nello spazio utente.Quindi, specificando un indirizzo dati non mappato a getsockopt, possiamo causare l'allocazione di un buffer nell'heap di
dimensione controllata, impedire che il contenuto di quel buffer venga inizializzato, e
poi raggiungere una chiamata a sooptcopyout che tenta di scrivere quel buffer di nuovo all'indirizzo non mappato.
Tutto ciò che dobbiamo fare perché il copyout abbia successo è rimappare quell'indirizzo tra le chiamate a
sooptcopyin e sooptcopyout. Se riusciamo a farlo, allora faremo trapelare dati non inizializzati dell'heap del kernel
nello spazio utente.
Si scopre che questa è una race condition piuttosto facile da vincere. Durante i test sul mio Macbook Pro del 2015, il numero
medio di tentativi per vincere la race non è mai stato superiore a 600, e la mediana non è mai stata superiore a 5.
Su iOS 10.1.1 su un iPhone 7 la race era ancora più facile da vincere, richiedendo in genere non più di 2
tentativi. (Questi test sono stati condotti con DEBUG disattivato, poiché le printf rallentano drasticamente
l'exploit.)
Per compilare, esegui make. Vedi l'inizio del Makefile per varie opzioni di compilazione.
Esegui l'exploit specificando la dimensione della fuga da tentare sulla riga di comando:
$ 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
Ho segnalato questo problema ad Apple il 7 ottobre 2017. Gli è stato assegnato CVE-2017-13868. Apple ha corretto il problema in macOS 10.13.2 e iOS 11.2.
Il codice di ctl_ctloutput-leak è rilasciato nel pubblico dominio. Per cortesia chiedo che, se fai riferimento o usi parte di questo codice, me lo attribuisca.