
CVE-2017-13868: Fuga de información de datos no inicializados del heap del kernel en XNU.
La función ctl_ctloutput en macOS High Sierra 10.13 ignora el valor de retorno de una llamada a sooptcopyin, lo que abre una ventana de carrera para filtrar datos no inicializados del heap del kernel al espacio de usuario. ctl_ctloutput-leak es un exploit de prueba de concepto que intenta desencadenar esta fuga de información. La explotación requiere privilegios de root.
Se ha confirmado que este exploit funciona en macOS High Sierra 10.13.1 Beta 17B25c e iOS 10.1.1 14B100 (bajo mach_portal).
Aquí está la parte relevante de ctl_ctloutput en 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.
Este código hace lo siguiente:
getsockopt, sin especificar el indicador M_ZERO para poner a cero los bytes asignados.getsockopt desde el espacio de usuario usando sooptcopyin, llenando el búfer de datos recién asignado. Se supone que este copyin sobrescribe por completo los datos asignados, razón por la cual no se necesitaba el indicador M_ZERO. Sin embargo, el valor de retorno de sooptcopyin no se comprueba, lo que significa que es posible que el copyin haya fallado, dejando datos no inicializados en el búfer. El copyin podría fallar si, por ejemplo, el programa pasara una dirección no asignada a getsockopt.getsockopt para este socket de control del kernel. Esta implementación debería procesar el búfer de entrada, posiblemente modificándolo y acortándolo, y devolver un código de resultado. Sin embargo, la implementación es libre de asumir que el búfer proporcionado ya ha sido inicializado (ya que teóricamente proviene del espacio de usuario), y por lo tanto varias implementaciones no modifican el búfer en absoluto. La función NECP necp_ctl_getopt, por ejemplo, simplemente devuelve 0 sin procesar el búfer de datos en absoluto.Por lo tanto, al especificar una dirección de datos no asignada a getsockopt, podemos hacer que se asigne un búfer de heap de tamaño controlado, impedir que el contenido de ese búfer sea inicializado, y luego alcanzar una llamada a sooptcopyout que intenta escribir ese búfer de vuelta a la dirección no asignada. Todo lo que necesitamos para que el copyout tenga éxito es reasignar esa dirección entre las llamadas a sooptcopyin y sooptcopyout. Si podemos hacer eso, entonces filtraremos datos no inicializados del heap del kernel al espacio de usuario.
Resulta que esta es una carrera bastante fácil de ganar. Durante las pruebas en mi Macbook Pro de 2015, el número medio de intentos para ganar la carrera nunca fue superior a 600, y la mediana nunca fue superior a 5. En iOS 10.1.1 en un iPhone 7 la carrera era aún más fácil de ganar, normalmente no requería más de 2 intentos. (Estas pruebas se realizaron con DEBUG desactivado, ya que los printfs ralentizan drásticamente el exploit.)
Para compilar, ejecuta make. Consulta la parte superior del Makefile para las distintas opciones de compilación.
Ejecuta el exploit especificando el tamaño de fuga objetivo en la línea de comandos:
$ 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
Informé de este problema a Apple el 7 de octubre de 2017. Se le asignó CVE-2017-13868. Apple corrigió los problemas en macOS 10.13.2 y iOS 11.2.
El código de ctl_ctloutput-leak se publica en el dominio público. Como cortesía, pido que si haces referencia o usas parte de este código, me lo atribuyas a mí.
getsockopt no devuelve un error, ctl_ctloutput llama a sooptcopyout para copiar el búfer de datos de vuelta al espacio de usuario.