Por: Jon Masters, principal arquitecto de ARM, Red Hat
En Red Hat hemos estado trabajando en la mitigación de posibles ataques con sujeción a la prohibición estándar de informar problemas de seguridad, asignando equipos pequeños y específicos que trabajan sólo con la información necesaria, con el propósito de prepararnos antes de su divulgación. Tuve la suerte de compartir la dirección de nuestros esfuerzos de mitigación de Meltdown y Spectre, conocidos también como variantes 1, 2 y 3 de un grupo de ataques similares difundidos por Google Project Zero a través de un blog publicado el 3 de enero. Durante estas tareas, reprodujimos Meltdown (variante 3) en nuestros laboratorios y estudiamos otras variantes, y simultáneamente trabajamos con varios de nuestros socios de hardware de confianza en las medidas de mitigación.
Si bien comprendemos cabalmente estas vulnerabilidades y el análisis actual de los factores determinantes, así como los parches para mitigar su posible impacto, continuaremos colaborando con nuestros socios, clientes e investigadores en esta situación. Asimismo, nos gustaría ayudar a que otras personas comprendan estos problemas complejos, idealmente mediante el uso de un lenguaje y términos que no requieran que el lector trabaje en el diseño de chips. Para quienes deseen profundizar los detalles técnicos, los trabajos de investigación originales y publicaciones relacionadas están disponibles en http://meltdownattack.com/ y http://spectreattack.com/, pero también sirve tener en cuenta que muchas de las personas involucradas en identificar estos ataques poseen una gran experiencia en la investigación académica de arquitecturas informáticas. Al menos una de ellas el año pasado obtuvo un doctorado en un área relacionada. Por eso, no debe afligirse si se requieren varios intentos para ahondar en los detalles técnicos; se trata de un tema sumamente complejo y detallado.
Para comenzar, conozcamos un poco qué es la “ejecución especulativa” a partir de una analogía cotidiana.
Supongamos que un cliente habitual todos los días visita la misma cafetería y pide la misma bebida con cafeína. Con el paso del tiempo, el cliente empieza a conocer a los camareros, que a su vez se familiarizan con el pedido de este cliente. Para brindarle una buena atención (y ahorrarle al preciado cliente el tiempo de espera en la fila), los camareros finalmente deciden empezar a preparar el pedido del cliente apenas éste los saluda al ingresar al local. Pero un día, el cliente modifica su pedido. En ese caso, el camarero debe descartar el café preparado con anticipación y preparar uno nuevo mientras el cliente espera.
Si avanzamos aún más en esta analogía, supongamos que los camareros conocen el nombre del cliente y les gusta escribirlo con un marcador indeleble en su taza. Cuando de manera especulativa preparan la bebida habitual, escriben el nombre del cliente en la taza. Si el cliente viene con un pedido diferente, la taza en cuestión se descarta junto con su contenido. Pero al hacerlo, la información personal identificable que figuraba en la taza queda visible por un breve instante para cualquier observador.
Este escenario de la cafetería implica especulación. El personal no sabe a ciencia cierta si cuando el cliente ingrese al local pedirá un café con leche o un americano, pero por la información histórica sabe lo que el cliente habitualmente pide y hace una deducción fundada para ahorrarle tiempo de espera. Este tipo de especulación ocurre a diario en nuestras vidas porque con frecuencia esas deducciones demuestran ser acertadas y, como resultado, podemos lograr hacer más en la misma cantidad de tiempo. Lo mismo sucede con nuestras computadoras. Éstas utilizan una técnica conocida como “ejecución especulativa” para realizar determinadas operaciones de procesamiento antes de que se sepa con certeza que se requerirán tales operaciones, sobre la premisa de que estas deducciones a menudo terminan ahorrando tiempo.
En el caso de las computadoras, la ejecución especulativa se utiliza para decidir qué hacer ante una prueba del estilo: “en caso de A, hacer B; de lo contrario, hacer C”. Éstas se denominan condiciones de prueba y el código que se ejecuta como resultado es parte de lo que llamamos una bifurcación condicional. Una bifurcación sólo significa una sección del programa que elegimos ejecutar en respuesta a cualquiera sea el resultado de la condición. Los chips informáticos modernos poseen “predictores de bifurcaciones” sofisticados que emplean algoritmos elaborados para determinar el resultado probable de una prueba condicional mientras aún se calcula esa prueba. En el ínterin, si la deducción demuestra ser acertada, el chip pareciera funcionar más rápido que el esperar a que se complete la prueba. Si la deducción resulta errada, el chip debe eliminar los resultados especulativos y ejecutar la otra bifurcación. Los predictores de bifurcaciones con frecuencia tienen más de un 99% de exactitud en sus deducciones.
Como se puede observar, el beneficio potencial en términos de rendimiento de que un chip ejecute de forma especulativa la bifurcación correcta del código es considerable. De hecho, la ejecución especulativa es una de las tantas optimizaciones que han ayudado a acelerar drásticamente los computadores durante las últimas dos décadas. Cuando se la implementa en la forma correcta, la ventaja resultante en el rendimiento es significativa. El origen de los problemas recientemente detectados proviene de los intentos del diseño de los chips de lograr una mayor optimización suponiendo que el proceso de especulación es una caja negra totalmente invisible para los observadores externos (o delincuentes).
La creencia generalizada de la industria era que todo lo que sucediera durante el proceso de especulación (conocido como “ventana de ejecución especulativa”) quedaría luego confirmado y los resultados serían utilizados por el programa o, por el contrario, no eran utilizados y eran descartados por completo. Pero resulta ser que existen modos de que los atacantes visualicen lo que sucede dentro de la ventana de especulación y, en consecuencia, manipulen el sistema. Un atacante también puede dirigir el comportamiento de los predictores de bifurcaciones para hacer que determinadas secuencias de código —que normalmente nunca habrían sido ejecutadas— sean ejecutadas de forma especulativa. Confiamos en que estas vulnerabilidades y otros fallos similares que podrían aprovechar la ejecución especulativa deriven en cambios fundamentales en la forma en que se diseñen los futuros chips, de modo tal que podamos contar con la ejecución especulativa sin los riesgos de seguridad.
Veamos ahora los ataques en mayor detalle, comenzando con Meltdown (variante 3), que tuvo una gran repercusión debido a su amplio impacto. En esta forma de ataque, se engaña al chip para que realice la carga de datos protegidos durante una ventana de especulación de modo tal que los mismos puedan ser luego visualizados por un atacante no autorizado. El ataque depende de una práctica amplia y comúnmente utilizada en la industria que separa la carga de datos en memoria del proceso de verificación de permisos. Una vez más, la creencia generalizada de la industria operaba bajo el supuesto de que todo el proceso de ejecución especulativa era invisible, por lo que la separación de estas piezas no se consideraba un riesgo.
En Meltdown, una bifurcación de código cuidadosamente elaborada primero se dispone a ejecutar cierto código de ataque de manera especulativa. Este código carga algunos datos protegidos a los cuales el programa normalmente no tiene acceso. Como esto sucede en forma especulativa, la verificación de permisos respecto de ese acceso ocurrirá en paralelo (y no fracasará hasta la finalización de la ventana de especulación) y, como consecuencia, cierta memoria interna especial del chip conocida como memoria caché quedará cargada con información privilegiada. Luego, se utiliza una secuencia de código meticulosamente construida para realizar otras operaciones de memoria en función del valor de la información privilegiada. En tanto los resultados normalmente observables de estas operaciones no son visibles luego de la especulación (la cual se descarta en última instancia), se puede utilizar una técnica conocida como análisis de canal lateral de la caché para determinar el valor de los datos protegidos.
Mitigar Meltdown implica cambiar la forma en que se administra la memoria entre el software de la aplicación y el sistema operativo. En Red Hat hemos introducido una nueva tecnología, conocida como KPTI (Kernel Page Table Isolation) que separa la memoria de forma tal que los datos protegidos no pueden ser cargados en la memoria caché interna del chip mientras se ejecuta el código del usuario. El adoptar medidas adicionales cada vez que un software de aplicación le pide al sistema operativo hacer algo en su nombre (lo que denominamos “llamadas al sistema”) termina teniendo un impacto en el rendimiento. El nivel de este impacto varía aproximadamente en función de la frecuencia con la que una aplicación necesite utilizar los servicios de ese sistema operativo.
El ataque Spectre consta de dos partes. La primera (variante 1) tiene que ver con la violación de la “comprobación de límites”. Una vez más, cuando se ejecute código de manera especulativa, el chip podría cargar ciertos datos que luego se utilizarían para ubicar un segundo grupo de datos. Como parte de una optimización del rendimiento, el chip podría intentar cargar de modo especulativo el segundo grupo de datos antes de haber validado que el primero se encuentre dentro de un rango definido de valores. Si esto sucediera, es posible hacer que el código ejecute de forma especulativa y lea datos que no debería en la memoria caché del sistema, desde la cual pueden ser extraídos utilizando un ataque de canal lateral similar al analizado anteriormente.
Mitigar la primera parte de Spectre implica agregar lo que denominamos “barreras de carga” en todo el kérnel. Estas barreras impiden que el hardware de especulación intente realizar una segunda carga basada en una primera carga. Esto requiere efectuar cambios pequeños y triviales en toda la fuente del kérnel que no afectan necesariamente el rendimiento. Nuestro equipo de herramientas ha desarrollado algunas herramientas y ha trabajado con otras personas para ayudar a determinar dónde deberían colocarse estas barreras de carga.
La segunda parte de Spectre (variante 2) en cierta mantera es la que resulta más interesante. Tiene que ver con “entrenar” al hardware predictor de bifurcaciones para que favorezca de manera especulativa la ejecución de partes del código distintas a las que debería estar ejecutando. Una optimización de hardware común consiste en basar el comportamiento de una opción de bifurcación en la ubicación en memoria del mismo código de bifurcación. Desafortunadamente, la forma en que se almacena esta ubicación en memoria no es específica entre una aplicación y el kérnel del sistema operativo. Esto permite que el predictor sea entrenado para que ejecute de manera especulativa cualquier código que desee el atacante. Mediante la selección cuidadosa de un “gadget” (código existente en el kérnel que tiene acceso a información privilegiada), el atacante puede cargar información confidencial en la memoria caché del chip, donde el mismo tipo de ataque de canal lateral servirá una vez más para extraerla.
Uno de los problemas más importantes que plantea esta segunda parte de Spectre es su potencial para sacar provecho del límite existente entre el kérnel del sistema operativo y un hipervisor, o entre distintas máquinas virtuales que estén siendo ejecutadas sobre el mismo hardware. El predictor de bifurcaciones puede ser entrenado por una máquina virtual para lograr que código privilegiado presente en el hipervisor (u otra instancia de máquina virtual) acceda a información fiable del hipervisor que puede ser extraía utilizando un canal lateral. Esto representa un riesgo considerable para los entornos de nube pública y privada que operan con servidores sin actualizaciones.
Mitigar esta segunda parte de Spectre exige que el sistema operativo inhabilite (de manera selectiva) el hardware de predicción de bifurcaciones cada vez que un programa solicite los servicios de un sistema operativo (llamada al sistema) o de un hipervisor, de modo tal que todo intento de un código malicioso de entrenar al predictor no se trasladará al kérnel del sistema operativo, al hipervisor o entre máquinas virtuales no fiables que se ejecuten en el mismo servidor. Este enfoque funciona bien, pero tiene un impacto significativo en el rendimiento. Los parches de Red Hat implementarán este cambio en la seguridad en forma predeterminada y aceptarán el impacto en el rendimiento, pero también hemos agregado la posibilidad de que los administradores de sistemas activen o desactiven esta opción (y todas las configuraciones implementadas). Asimismo, estamos trabajando con la gran comunidad de Linux para reducir este impacto conforme avanza el tiempo mediante el estudio de alternativas para inhabilitar la predicción de bifurcaciones. Una de las posibles alternativas se conoce como “retpoline”, una forma especialmente ideada de ejecutar el código del kérnel de un sistema operativo que evita la especulación de bifurcaciones incorrectas.
Esperamos que esta publicación haya arrojado un poco más de luz sobre estos ataques tan sofisticados. Su aprovechamiento no es para nada banal, las mitigaciones son posibles y aunque ya existan disponibles algunos ejemplos en Internet para Meltdown (variante 3), existen parches disponibles a través de actualizaciones provistas por proveedores líderes como Red Hat. Con el paso del tiempo, se podrán descubrir otras vulnerabilidades relacionadas y se podrán publicar en Internet códigos de ejemplo para sacar provecho de ellas, debido a lo cual es fundamental mantenerse al día con los parches de seguridad a medida que estén disponibles.
Es importante recordar que recién estamos en la etapa inicial posterior al descubrimiento de una clase totalmente nueva de vulnerabilidades de seguridad informática y, como resultado, las mitigaciones y el asesoramiento sobre las buenas prácticas relacionadas podrán variar con el transcurso del tiempo. Continuaremos trabajando junto a los líderes de la industria y las comunidades open source para proteger a nuestros clientes de estas y otras vulnerabilidades conocidas y fortalecer a Linux aún más frente a ataques como Meltdown y Spectre. En los próximos meses publicaremos más artículos sobre estos esfuerzos y actualizaremos el asesoramiento a nuestros clientes en relación con nuestros productos
Para conocer más, visite https://access.redhat.com/security/vulnerabilities/speculativeexecution