Info
Content

Meltdown

En este artículo explicamos de forma muy simplificada qué es y cómo funciona el ataque meltdown.

Con fines didácticos hicimos muchas omisiones y simplificaciones que pueden restarle precisión al ataque y a la información técnica, pero el objetivo de este artículo es que se entienda la dinámica del ataque y no los detalles del funcionamiento.

Qué es meltdown

Es una forma de explotar el funcionamiento de varias familias de microprocesadores para copiarse la memoria completa[1].

Copiarse la memoria completa implica tener acceso a regiones de memoria privilegiadas donde hay datos críticos como la llave de cifrado de particiones, llaves gpg, y todos los secretos que puedan imaginarse que guardan los programas y el sistema operativo en memoria.

Cómo funciona el microprocesador

Para el microprocesador la memoria está dividida en espacio de usuario (EU) y espacio de kernel (EK). Cada vez que el microprocesador ejecuta una instrucción valida los privilegios, si un programa del EU trata de mover memoria del EK a algún registro, el microprocesador falla.

Cada núcleo del microprocesador parte una instrucción en µOps o "micro instrucciones". Internamente el núcleo tiene "unidades de ejecución" (UE) que saben cómo ejecutar cada µOp específico. Algunas UE son la ALU (ejecuta operaciones aritméticas), la unidad de lectura de memoria y la unidad de escritura de memoria, entre otras.

Por ejemplo, la instrucción POPA para bajar datos del stack a un registro general se parte en 16 µOps o micro instrucciones. Por la cantidad de recursos que necesita (tiempo de las unidades de ejecución), podemos decir que POPA es una instrucción LENTA[2].

Para optimizar velocidad al ejecutar instrucciones lentas como POPA, el microprocesador tiene un mecanismo predictivo (branch predictor[3]) que evalúa cuáles son las próximas instrucciones a ejecutar del programa, y si tiene unidades de ejecución disponibles, las ejecuta en paralelo a la instrucción lenta. Esta estrategia se llama out-of-order execution o ejecución desatendida.

Cuando termina de ejecutarse la instrucción lenta, todas las otras instrucciones que predijo el branch predictor también terminaron de ejecutarse y escribieron sus resultados en la memoria cache del microprocesador (L1, L2 y L3). El microprocesador continúa ejecutando las siguientes instrucciones y como ya fueron ejecutadas gracias al mecanismo predictivo, no las vuelve a ejecutar, simplemente lee los resultados de la memoria cache.

El problema

El problema está en que la ejecución desatendida (out-of-order execution), al contrario de la ejecución normal del microprocesador, no valida privilegios. Por lo tanto, si algunas instrucciones de un programa de usuario se ejecutan gracias al mecanismo predictivo y leen datos desde el espacio de kernel de la memoria, estos datos se escribirán en la cache del microprocesador evadiendo la validación de privilegios.

El ataque consiste en usar este problema de la ejecución desatendida para escribir datos privilegiados en la cache interna del microprocesador, y luego leer esos datos[4]:

  1. Se escribe un programa que tenga una instrucción lenta.
push 0xdebf    ; coloca un valor en el stack
popa eax       ; eax ahora tiene el valor 0xdebf. Esta es la instrucción lenta.
  1. Justo después de la instrucción lenta se coloca una instrucción que provoque un error. Por ejemplo, división por cero:
mov edx, 0      ; borra el dividendo
mov eax, 0x10   ; dividendo
mov ecx, 0x0    ; divisor
div ecx         ; EAX / ECX -> error de division por cero

En este punto cuando se produzca el error, gracias al mecanismo predictivo se habrán ejecutado las instrucciones siguientes.

  1. Justo después de la instrucción que produce el error, se escriben las instrucciones que leen la memoria privilegiada.
mov eax, [0xe0000]      ; copia alguna dirección de memoria del kernel
  1. El error de división por cero corta la ejecución, pero el mecanismo predictivo hizo que la instrucción del punto 3 se ejecutara, y por cómo funciona la ejecución desatendida la instrucción mov escribió los datos en la memoria cache interna del microprocesador sin validar privilegios.

  2. Luego de terminar debido al error de división por cero, el programa que está realizando el ataque lee los datos que la instrucción mov del punto 3 escribió en la memoria cache del microprocesador. Para esto utiliza una técnica de ataque que se llama Flush+Reload, un ataque de la familia cache side-channel attacks[5]. Este ataque consiste en utilizar la función clflush de la librería estándar de C para forzar la recarga de la memoria cache del microprocesador y determinar si se escribieron datos a partir del tiempo que tarda esta función.

La solución

No hay soluciones para este ataque. Los sistemas operativos están subiendo parches para deshabilitar el mecanismo predictivo de los microprocesadores, pero esto implica que los microprocesadores funcionarán considerablemente más lentos[6][7].

Referencias

  1. https://meltdownattack.com/meltdown.pdf
  2. http://www.agner.org/optimize/instruction_tables.pdf
  3. https://danluu.com/branch-prediction/
  4. Prueba de concepto del ataque: https://github.com/gkaindl/meltdown-poc/blob/master/meltdown.c
  5. https://eprint.iacr.org/2013/448.pdf
  6. Ya hay reportes de pérdida de velocidad en sistemas que manejan volúmenes grandes de datos: https://twitter.com/chanian/status/949457156071288833
  7. En este repositorio están actualizando bastante rápido el estado de los parches para cada sistema operativo: https://github.com/hannob/meltdownspectre-patches
No Comments
Back to top