
CVE-2018-4185: divulgación de punteros del kernel de iOS 11.2-11.2.6 introducida por la mitigación de Meltdown de Apple.
iOS 11.2 introdujo una fuga de información del kernel que podía utilizarse para determinar el desplazamiento de kASLR. El problema fue el resultado de una función recién agregada, __ARM_KERNEL_PROTECT__, que sin querer provocó que la dirección de la función del kernel Lel0_synchronous_vector_64_long apareciera en el registro x18 al obtener los valores de los registros de un hilo usando thread_get_state. El problema se descubrió cuando comenzaron a aparecer punteros del kernel en los registros de fallos de aplicaciones iOS.
En iOS 11.2, Apple introdujo una función en arm64 llamada __ARM_KERNEL_PROTECT__. Según un comentario en osfmk/arm64/proc_reg.h:
__ARM_KERNEL_PROTECT__es una función diseñada para proteger contra posibles vulnerabilidades arquitectónicas o microarquitectónicas que podrían permitir que los núcleos lean/accedan a asignaciones solo de EL1 mientras están en modo EL0. Esto se logra eliminando tantas asignaciones como sea posible cuando el núcleo pasa del modo EL1 al modo EL0, y restaurando esas asignaciones cuando el núcleo pasa del modo EL0 al modo EL1.
Es decir, al pasar de EL1 (modo kernel) a EL0 (modo usuario), se eliminarán tantas asignaciones del kernel como sea posible. Esto debería limitar la posible superficie de ataque contra las asignaciones de memoria del kernel al explotar vulnerabilidades microarquitectónicas como Spectre o Meltdown.
Si observa la diferencia entre las versiones de XNU 4570.20.62 y 4570.31.3, encontrará varias referencias nuevas al registro x18 que aparecen en el archivo osfmk/arm64/locore.s en relación con __ARM_KERNEL_PROTECT__. En particular, verá que el vector de excepción Lel0_synchronous_vector_64, que es el vector de excepción invocado en una llamada al sistema (instrucción svc #0), ahora tiene este aspecto:
.text
.align 7
Lel0_synchronous_vector_64:
MAP_KERNEL
BRANCH_TO_KVA_VECTOR Lel0_synchronous_vector_64_long, 8
La macro BRANCH_TO_KVA_VECTOR se define como:
.macro BRANCH_TO_KVA_VECTOR
#if __ARM_KERNEL_PROTECT__
/*
* Find the kernelcache table for the exception vectors by accessing
* the per-CPU data.
*/
mrs x18, TPIDR_EL1
ldr x18, [x18, ACT_CPUDATAP]
ldr x18, [x18, CPU_EXC_VECTORS]
/*
* Get the handler for this exception and jump to it.
*/
ldr x18, [x18, #($1 << 3)]
br x18
#else
b $0
#endif /* __ARM_KERNEL_PROTECT__ */
.endmacro
Esta macro realiza un salto indirecto a la implementación real del vector de excepción, Lel0_synchronous_vector_64_long, cargando un puntero a esa función en el registro x18. Sin embargo, observe que esta modificación de x18 ocurre antes de que los registros del espacio de usuario sean guardados por la función fleh_dispatch64, que es llamada por Lel0_synchronous_vector_64_long. Esto significa que cuando se guardan los registros de usuario, x18 será en realidad un puntero a Lel0_synchronous_vector_64_long en lugar del valor original del espacio de usuario.
Aunque x18 se limpia al regresar de la excepción, almacenar un puntero del kernel en el estado de registro de usuario es problemático porque thread_get_state se puede usar para copiar el estado de registro de usuario guardado de vuelta al espacio de usuario, incluido el valor del registro x18. Todo lo que un hilo necesita hacer para obtener la dirección de la función Lel0_synchronous_vector_64_long es llamar a thread_get_state sobre sí mismo y mirar el valor informado de x18. Esto hace que sea trivial determinar el desplazamiento de kASLR restando el valor de x18 así obtenido de la dirección estática de Lel0_synchronous_vector_64_long.
Como se mencionó anteriormente, la explotación es trivial: simplemente llame a la función thread_get_state, observe el valor del registro x18 y réstele la dirección estática de la función del kernel Lel0_synchronous_vector_64_long.
Descubrí este problema el 26 de febrero de 2018, después de notar un puntero del kernel en el registro x18 de un registro de fallos de una aplicación iOS. Una verificación rápida mostró que el mismo valor aparecía en el registro x18 de todos los registros de fallos en el dispositivo, lo que sugería una fuga de información grave.
A continuación, intenté determinar exactamente qué estaba sucediendo con el registro x18 mediante la experimentación. Establecí un punto de interrupción en una aplicación iOS vacía y usé lldb para leer el valor del registro x18, confirmando que la fuga no se limitaba a aplicaciones que fallaban. Luego intenté leer el valor de x18 usando ensamblador en línea y descubrí que el valor obtenido no coincidía con el valor mostrado por el depurador al usar un comando como reg read x18. Esto sugirió que quizás la fuga estaba realmente en thread_get_state, y que el registro x18 no contenía realmente un puntero del kernel mientras la CPU ejecutaba en espacio de usuario. Una prueba de concepto rápida que leyó el valor de x18 usando thread_get_state confirmó que esta función era efectivamente la fuente de la fuga.
Informé el problema a Apple el 26 de febrero de 2018, el mismo día que lo descubrí.
Por Brandon Azad