Antes de entender el problema hemos de entender lo que es la Ejecución fuera de Orden de los procesadores que es el origen de todo. ¿De donde viene la Ejecución Fuera de Orden? Pues es una aplicación a nivel de hardware del Algoritmo de Tomasulo.

El problema común de Meltdown y Spectre es la ejecución especulativa que es algo que tienen en común todos los Procesadores Fuera de Orden.

En ciencias de la computación, la ejecución especulativa es la ejecución de código por parte del procesador que no tiene por qué ser necesaria a priori

La ejecución especulativa no es más que una optimización. Obviamente, sólo es útil cuando la ejecución previa requiere menos tiempo y espacio que el que requeriría la ejecución posterior, siendo este ahorro lo suficientemente importante como para compensar el esfuerzo gastado en caso de que el resultado de la operación nunca llegue a usarse.

La ejecución especulativa se puede activar por varios motivos que iremos viendo en la entrada.

#1.1 Meltdown

Meltdown funciona en tres pasos distintos:

  1. Paso 1: El programa atacante elige una dirección de memoria que es inaccesible para el atacante y la carga en un registro.
  2. Una instrucción transitoria accede a la linea de cache basada en el contenido secreto del registro.
  3. El programa atacante utiliza Flush+Reload para determinar la linea de cache accedida y por tanto el secreto almacenado en la dirección de memoria escogida.

Repitiendo los pasos para las diferentes localizaciones de memoria, el ataquente puede verter la memoria del kernel, incluyendo la memoria física al completo.

¿Y como de complejo es Meltdown?

1 ; rcx = kernel address
2 ; rbx = probe array
3 retry:
4 mov al, byte [rcx]
5 shl rax, 0xc
6 jz retry
7 mov rbx, qword [rbx + rax]

La ejecución de instrucciones de Meltdown en un núcleo. La inaccesible dirección del kernel es movido
a un registro, creando una excepción. Las subsecuentes instrucciones son ya ejecutadas fuera de orden antes
de que la excepción sea lanzada, filtrando el contenido de la dirección del kernel a través de un acceso indirecto a la memoria.

Si… Se cual es vuestra reacción en estos momentos:

El objetivo de Meltdown es acceder al espacio de memoria virtual del kernel. Los PCs utilizan dos espacios de memoria virtual distintos, uno a nivel de Sistema Operativo (kernel) y el otro a nivel de Usuario, el espacio de memoria virtual para el Kernel no es accesible por tanto por las aplicaciones que se ejecutan en el modo protegido. El espacio de direcciones del Kernel no solo tiene la memoria mapeada para su propio uso, pero también necesita realizar operaciones en las páginas de la memoria virtual a nivel de Usuario. Coo consecuencia en SOs como Linux y macOS esta mapeado en el kernel y en ambos casos esta hecho con un mapa de acceso físico directo.

Esta particularidad ya existía a partir del 80286… cuandos se añadio la MMU a los núcleos de Intel. Lo que necesitamos para filtrar los datos del Kernel es saber donde se encuentra la tabla de paginación, la cual se suele encontrar en la

Translation Lookaside Buffer (TLB) es una memoria caché administrada por la MMU, que contiene partes de la tabla de paginación, es decir, relaciones entre direcciones lógicas y físicas. Posee un número fijo de entradas y se utiliza para obtener la traducción rápida de direcciones. Si no existe una entrada buscada, se deberá revisar la tabla de paginación y tardará varios ciclos más, sobre todo si la página que contiene la dirección buscada no está en memoria primaria (véase memoria virtual). Si en la tabla de paginación no se encuentra la dirección buscada, saltará una interrupción conocida como fallo de página.

Normalmente no tenemos acceso a los datos de la table de paginación pero lo que consigue Meltdown es darnos acceso a las direcciones de memoria de la misma y abrir la caja de pandora y eso sin tener acceso al TLB de manera directa. La explicación a como funciona el código nos aclara como las instrucciones «ilegales» son cargadas sin problemas en el procesador pero no son ejecutadas en principio.

4 mov al, byte [rcx]

Cargamos el valor de byte localizado en la dirección objetivo del kernel, el cual se almacena en el registro RCX en el byte menos significante del registro RAX representado por AL. Como explicamos en la seccio´n 2.1, la intrucción MOV es captada por el núcleo, descodificada en micro-operaciones, asignada y enviada al búfer de reordenamiento. Aquí los registros arquitecturals (RAX y RCX) están mapeados a una serie de registros físicos que permiten la ejecución fuera de orden. Intentando utilizar el pipeline en lo mázimo posible, las siguientes instrucciones…

5 shl rax, 0xc
6 jz retry
7 mov rbx, qword [rbx + rax]

ya están descodificadas y asignadas como microoperaciones. Las microoperaciones son enviadas a la estación de reserva manteniendo el microcódigo mientras estas esperan ser ejecutadas en su correspondiente unidad de ejecución.

Es decir, el procesador incluso sabiendo que no tiene que ejecutar esas instrucciones que comprometen la seguridad del sistema porque permiten el acceso de los datos del modo protegido en modo usuario. ¿Y esto a que es debido? A lo que se llama ejecución especulativa que es algo que tienen todos los procesadores fuera de orden independientemente del conjunto de registros e instrucciones tal y como hemos visto en la instrucción

 

En el caso de la Variante 3/Meltdown no hay la explotación de una ramificación ni por fuera de limites como ocurre con Spectre (tratado más abajo en esta entrada), aquí simplemente se ejecuta siempre el código a nivel de procesador independientemente de si esta permitido o no por el sistema operativo. Y es aquí donde empiezan los problemas de verdad porque esto lo lleva arrastrando Intel no desde hace unos diez años sino desde el Pentium Pro. En el manual de desarrollo de software de Intel especifica en el Volumen 3A, sección 11.7  («Implicit Caching (Pentium 4, Intel Xeon, and P6 family processors») se puede leer lo siguiente:

El cacheado implicito se produce cuando un elemento de la memoria es potencialmente cacheable, pese a que el elemento no será jamás accedido en la secuencia Von Newmann habitual. El caching implicito ocurre en los P6 (Pentium Pro, Pentium II, Pentium III) y las familias de procedores más recientes debido a la precaptación agresiva, a la predicción de salto y al manejo de perdisas del TLV. El caching implicito es una extensión del comportamiento existente en los Intel 386, Intel 486 y procesadores Intel Pentium, desde que el software corriendo en dichas familias de procesadores no han sido capaces de predecir el comportamiento de la pre-captación de una instrucción

Las instrucciones en modo especulativo son siempre ejecutadas por adelantado independientemente de si van a ser solventadas o no… ¿Y donde almacena los datos de los resultados hasta que tiene la confirmación de que la serie de instrucciones son legales o no?

Si la serie de instrucciones están permitidas entonces el Reorder Buffer copia el resultado al espacio de la memoria requerido, si no lo hace simplemente lo elimina por lo que el funcionamiento normal sería que simplemente esos datos no se copiaran pero para hacerlo se hace un FLUSH+RELOAD que es un tipo de ataque concreto a las caches y las convierte en un coladero de datos de manera no planeada, porque en realidad lo que nos interesa es volcar el contenido del Reorder Buffer en algún lugar para recuperar los datos.

El termino Flush lo podríamos traducir como «tirar de la cadena» y la trampa esta en vaciar el contenido de la Cache directamente sobre la memoria RAM. Por lo que el contenido de la ejecución ilegal que se ha realizado durante la ejecución especulativa. ¿En principio esto debería afectar a todos los procesadores fuera de orden con ejecución especulativa no? ¿Entonces como es que Intel es la afectada? AMD sostiene que las diferencias arquitecturales es lo que hacen que el código ilegal no se ejecute. La propia documentación del Meltdown es muy clara en que en principio los procesadores de AMD y de ARM no sufren el problema.

Estamos intentando reproducir el bug Meltdown en diversas CPUs de ARM y AMD. Sin embargo no hemos conseguido con éxito filtrar la memoria del kernel con el ataque descrito en la Sección 5, ni en ARM ni en AMD. Los motivos de ellos pueden ser muchas, primero que nuestra implementación simplemente es demasiado lenta y una versión mejor optimizada puede tener éxito… Si el procesador carece de ciertas carácteristicas como el búfer de re-ordenamiento nuestra implementación actual no va a ser capaz de sustraer los datos. Sin embargo, tanto para ARM como para AMD, el ejemplo juguete descrito en la sección 3 funciona de manera confiable, indicando que la ejecución fuera de rden ocurre y que las instrucciones que hacen accesos ilegales a la memoria también son realizadas.

El ejemplo juguete es interesante de comentar porque va más allá de la micro-arquitectura.

En esta sección empezamos con un ejemplo de juguete, un pequeño retazo de código para ilustrar como la ejecución fuera de orden puede cambiar el estado microarquitectural de tal manera que filtre información.

1 raise_exception();
2 // the line below is never reached
3 access(probe_array[data * 4096]);

El retazo de código lo que hace es generar una excepción y luego acceder a un array. La propiedad de una excepción es que el flujode control no continua con el código después de una excepción, pero salta al manejador de excepciones del sistema operativo. Independientemente si la excepción ha sido realizada por un acceso no válido a la memoria, por ejemplo con el acceso a una dirección invalida o por otra excepción de la cPU como la división por cero, el flujo de control continua en el kernel y no con la siguiente instrucción en el espacio del usuario.

Así, nuestro ejemplo juguete no puede acceder al array en teoría, ya que la excepción inmediatamente es atrapada por el kernel que termina la aplicacíón. Sin embargo, dada la ejecución fuera de orden, la CPU puede haber ejecutado las siguientes instrucciones que no son dependientes de la excepción. Esto es ilustrado en la FIG. 3 debido a la excepción, las instrucciones ejecutadas fuera de orden no son retiraradas y por tanto no tienen efectos arquitecturales.

Pese a que las instrucciones que se ejecutan fuera de orden no tienen ningún esfuerzo visible a nivel arquitectural en los registros y la memoria si que tienen efectos laterales a nivel microarquitectural. Durante la ejecución fuera de orden, la memoria referenciada es captada en un registro y también almacenada en la cache. Si la ejecuciónfuera de orden es descartada el registro y los contenidos de la memoria no se ven comprometidos. Sin embargo, los contenidos cacheados de la memoria se mantienen en la cache. Podemos obtenerlos con un ataque lateral como es Flush+Reload.

Este ejemplo no es Meltdown… Pero ayuda a explicar la vulnerabilidad en los procesadores con ejecución fuera de orden… Es decir, es algo que es intrinseco de los procesadores fuera de orden. Cuando una CPU ejecutando de manera especulativa ya sea por hacer predicción de saltos o por existir una excepción tiene que tener un mecanismo que le permite a esta CPU ver si las instrucciones están fuera del nivel de privilegio. Pero no, Intel no ha equipado jamás a sus procesadores con dicho mecanismo. ¿Motivo? Porque en teoría el procesador solo tarda unas instrucciones en volver al estado anterior… ¿Entonces cual es la trampa? Como he dicho antes simplemente envias al procesador a buscar un dato que esta en la quinta puñeta para que el tiempo de la ejecución especulativa sea lo más largo posible y poder realizar el proceso en Meltdown.

Meltdown no conoce diferencias de Sistemas Operativos en teoría y aunque Spectre puede afectar también a Windows en el caso de Meltdown la diferencia esta en que no mantiene un mapa directo de la memoria física pero eso es completamente igual desde que Meltdown lo que hace es aprovechar el fallo de los procesadores de Intel para poder acceder al espacio de memoria que corresponde al Kernel del Sistema Operativo y es que independientemente de cual sea la naturaleza del Sistema Operativo en todos los sistemas contemporaneos hay siempre dos espacios de memoria, uno a nivel de usuario y el otro a nivel de sistema operativo que están fisicamente separados.

¿Puede afectar esto a PS4 y a Xbox One? En principio no debería porque ser así desde el momento en que Meltdown es un problema exclusivo de los Intel, pero no lo sabemos porque AMD esta en modo…

Lo que es Meltdown concretamente, no el otro ejemplo, solo afecta a los Intel pero han demostrado que es posible también con los ARM crear una excepción que coloque al procesador en modo especulativo. ¿Entonces como es que Meltdown afecta más a Intel que AMD? Al contrario que Spectre aquí la ejecución especulativa no se pone en funcionamiento a través de la predicción de saltos y con una rama indirecta sino a través de una excepción y simplemente ha sido suerte que la microarquitectura de los chips de Intel y AMD sea distinta…

¿Pe… pero no son x86-64 ambos?

Lo que tenemos son procesadores Post-RISC desde el Intel Pentium Pro en adelante, un procesador Post-RISC es un procesador CISC que tiene la particularidad de que el conjunto de registros e instrucciones complejas se traduce en un código RISC a nivel interno. ¿Motivo? El truco para alcanzar mayores velocidades de reloj es aumentar el número de etapas por instrucción pero las instrucciones CISC son dificilmente divisibles por lo que necesitan ser trasladadas en instrucciones RISC. Tanto AMD como Intel entienden las instrucciones y operaciones x86-64 pero internamente las instrucciones son descodificadas en una o varias micro-operaciones y esa descodificación funciona distinta en ambos tipos de procesador, esto no significa que los AMD sean inmunes a la explotación de la ejecución especulativa con tal de poder extraer los datos, solamente que el proceso en el caso de AMD tendría que ser distinto pero eso no significa que sea imposible.

 

 

#2.1 Spectre

A la hora de escribir esta enorme entrada es el que me ha dado más problemas, recordemos que Meltdown es el equivalente a la Variante 3 pero se definen como Spectre unas dos variantes distintas que son llamadas Variante 1 y Variante 2, en un principio esta sección iba al principio de la entrada y no estaba contrastada, pero digamos que la calificación por variantes viene de las tres formas en las que se realiza el ataque. La gente del Project Zero en Google ha realizado las investigaciones pertinentes creando tres piezas de código distintas para las tres variantes de ataque:

  • Variante 1: bounds check bypass (CVE-2017-5753) aka Spectre Version 1
  • Variante 2: branch target injection (CVE-2017-5715) aka Spectre Version 2
  • Variante 3: rogue data cache load (CVE-2017-5754) aka Meltdown (Del que ya hemos hablado)

En lo que se parecen Spectre y Meltdown es que ambos hacen uso de la ejecución especulativa pero mejor tengamos en cuenta el trabajo de los que lo han descubierto que han realizado una serie de pruebas de concepto con las tres variantes, aquí nos interesan solamente las dos variantes.

  1. Una prueba de concepto que demuestra los principios básicos de la variante 1 en el espacio de usuario testado en los Intel Haswell Xeon CPU, los AMD FX CPU, los AMD PRO CPU y un ARM Cortex A57. Esta Prueba de Concepto prueba solo la habilidad de leer los datos dentro de una ejecución por mala especulación dentro del mismo proceso, sin cruzar ninguno de los limites de privilegio.
  2. Una Prueba de Concepto de la variante 1, que cuando se ejecuta bajo los privilegios de usuarios de una kernel Linux moderna con una distribución estandar en la distribución puede realizar lecturas arbitrarías en la CPU Intel Xeon de la familia Haswell. Si el BPF JIT esta encendido (no es la configuración de serie) también funciona con al linea de CPUs AMDPro. En el núcleo Intel Haswell la memoria virtual del Kernel se puede leer a un ratio de 2000 bytes/seg después de 4 segundos del tiempo de arranque.
  3. Una pieza de código para la Variante 2, que ejecutandose con privilegios de root en el Kernel de una Máquina Virtual invitada utilizando el virt-manager en la CPU Intel Haswell Xeon, con una versión concreta de la distro Debian (ahora desfasada) ejecutandose en el huesped se puede leer la memoria del kernel del huesped a un ratio de 1500 bytes/s con margen a la optimización. Antes de que el ataque pueda ser realizado se tiene que realizar alguna inicialización que puede llevar entre 10 y 30 minutos para una máquina con 64GB, el tiempo necesario debería de escalar con la cantidad de RAM disponible en el huesped.

Cuando hice la entrada no había léido el Paper correspondiente por lo que leyendo en el blog del Project Zero pensaba de inicio que era un problema propio de Linux, pero no, en el Paper especifican que es posible utilizar Spectre para atacar a Windows también. ¿Cual es la idea detrás de Spectre y en que se diferencia de Meltdown? Hay dos formas de entrar en una ejecucion especulativa, la primera de ellas es a través de una excepción y la segunda es a través de la prediccion de saltos entrando en modo especulativo. Una excepción suele tener la particularidad de que se ataja facilmente porque el procesador es consciente de que no tiene que hacer dicha instrucción y ella misma se para o en teoría debería pararse, pero con la predicción de saltos es mucho más compleja.

Un salto a nivel de procesador ocurre cuando dada una condición el valor del contador de programa, el cual es secuencial y contiene la siguiente linea de memoria, a través de una instrucción de salto va y cambia el valor de dicho contador. El ejemplo más sencillo es el de un if simple, solo se salta si se la condición. ¿Y que hace la predicción de saltos? Simplemente en su sistema más simple lo que hace es a nivel de ejecución especulativa es procesar todo el proceso del salto para descartarlo si no se cumple o aceptarlo, en ese aspecto funciona exactamente igual que Meltdown pero mientras que Meltdown es una carrera contrarreloj, en el caso de Spectre la condición para que el procesador se ponga en modo especulativo es una condición de final del salto que va a tardar tiempo el procesador en descubrir que es falsa y que no se puede cumplir y esto es lo que hace a Spectre mucho peor que Meltdown porque este no solo afecta a los procesadores de Intel sino que afecta a todos los procesadores con ejecución fuera de orden y que por tanto hagan uso de ejecución especulativa y es completamente independiente del Sistema Operativo.

Pero como se ha dicho antes Spectre tiene dos metodologías distintas de realizarse, de ahí las dos variantes. La primera de ellas es curiosa porque se aprovecha de los bucles… ¿Que es un bucle? En el fondo es un salto recursivo que se da mientras se mantenga una condición. El salto convencional que se suele basar en instrucciones en alto nivel como if, else, switch… No son recursivos porque solo tienen una iteración y punto… Los que hayáis programado sabréis como es la sentencia de los for que tienen un contador incremental o decremental que se para cuando se llega al valor limite. Pues bien, os vais a reir pero la prediccion de saltos sabe que existe un salto pero no sabe si el salto es válido o no. Simplemente no tiene en cuenta el condicionante de fin de bucle sino que especulativamente lo ejecuta independientemente de si es válido o no… Suena risible pero es así, no podemos hacer un procesador que en la predicción de saltos se de cuenta que el valor que dispara la condicion de cese del bucle porque la variable utilizada para indexar el bucle no tiene el valor de cese.

¿Como es posible esto? Pues porque el procesador calcula todas las iteraciones futuras del bucle con la ejecución especulativa, es decir, se adelante a ella y lo descarta por completo dicha condición especulativa cuando se da cuenta que se da la condición de cese y esto es posible porque las instrucciones especulativas no se retiran sino que se encuentran en la Reservation Station que tienen todos los procesadores fuera de orden. Pero si lo pensamos bien el procesador siempre ejecutará especulatiavamente mientras la variable contador no supere el limite, por lo que si le damos a la cabeza nos encontramos que como mucho podrá ejecutar de manera especulativa el siguiente salto iterativo y es que la trampa al contrario de Meltdown aquí es distraer al proceador lo máximo posible… ¿como? Pues activando la trampa cuando se de una condición concreta que es cuando el bucle esta fuera de límite, si se realiza la trampa cuando se encuentra en los límites entonces el procesador se dará cuenta de la excepción por lo que para hacerlo viable tenemos que ir más allá. ¿pero como?

Pues con un salto indirecto, con un salto dentro del salto que también se va a ejecutar de manera especulativa pero que impedirá que el salto en el que esta integrado no se complete y dara el suficiente tiempo como para que el variable contador no entre en cese y al contrario que con Meltdown da el tiempo suficiente como para extraer los datos y contra esto los mecanismos contra las excepciones como ocurre con Meltdown no funcionan porque en la definicion más pura esta ejecutando un código completamente legal.

Pues si, porque esto es independiente de todo procesador, independiente de todo sistema operativo… Esto es aprovechar la forma de funcionamiento de los procesadores fuera de orden que son condición absoluta para la ejecución especulativa. Ahora bien, existe un problema añadido que hace vulnerables a los siguientes sistemas operativos:

  • La mayoria de la familia Unix BSD (FreeBSD especialmente)
  • Linux
  • macOS

Veamos el artículo del Project Zero, en concreto a la explicación del funcionamiento de la Variante 1 (Spectre)

Considerando el codigo de ejemplo de abajo, Si arr1->length no se encuentra en la cache, el procesador puede cargar especulativamente desde arr1->data[untrusted_offset_from_caller]. Esto es una lectura fuera de limites.Esto no debería importar debido a que el procesador efectivamente volverá hacía atrás la ejecucion con la rama se ejecute, ninguna de las instrucciones ejecutadas terminara de ejecutarse causando que los registros no se vean afectados.

struct array {

 unsigned long length;

 unsigned char data[];

};

struct array *arr1 = ...;

unsigned long untrusted_offset_from_caller = ...;

if (untrusted_offset_from_caller < arr1->length) {

 unsigned char value = arr1->data[untrusted_offset_from_caller];

 ...

}
Sin embargo, en el siguiente ejemplo de código, hay un problema If arr1->length, arr2->data[0x200] andarr2->data[0x300] no están en la cache pero si el resto de los datos, las predicciones de salto son predicidas como un tue, el procesador puede hacer lo siguiente de manera especulativa antes que arr1->length haya sido cargado y la ejecución haya sido re-dirigida been loaded and the execution is re-
  • value = arr1->data[untrusted_offset_from_caller]
  • start a load from a data-dependent offset in arr2->data, loading the corresponding cache line into the L1 cache
struct array {

 unsigned long length;

 unsigned char data[];

};

struct array *arr1 = ...; /* small array */

struct array *arr2 = ...; /* array of size 0x400 */

/* >0x400 (OUT OF BOUNDS!) */

unsigned long untrusted_offset_from_caller = ...;

if (untrusted_offset_from_caller < arr1->length) {

 unsigned char value = arr1->data[untrusted_offset_from_caller];

 unsigned long index2 = ((value&1)*0x100)+0x200;

 if (index2 < arr2->length) {

   unsigned char value2 = arr2->data[index2];

 }

}
Después de que la ejecución ha vuelto del camino especulativo debido a que el procesador se ha dado cuenta que untrusted_offset_from_caller es más grande que arr1->length,  la linea de cache conteniendo arr2->data[index2] se mantiene en la Cach L1. Midiendo el tiempo que se requiere para cargar  arr2->data[0x200] andarr2->data[0x300], un atacante puede determinar entonces si el valor de index2 durante la ejecución especulativa estaba en 0x200 o en 0x300 lo cual revela si  arr1->data[untrusted_offset_from_caller]&1 es 0 o 1.
Para ser capaz de utilizar este comportamiento para un ataque, el atacante necesita ser capaz de causar la ejecución de este código vulnerable en un contexto en un indice fuera de bordes. Para esto, el patrón vulnerable de código se ha de encontrar en código existente o tiene que existir un interprete o motor JIT que pueda generar el patrón de codigo vulnerable.
Hasta ahora no hemos identificado cualquier instancia existente de patrón de código vulnerable, el PoC para la perdida de la memoria de Kernel utiliando la primera variante utiliza el interprete eBPF JIT, el cual se encientra en el kernel y es accesible a los usuarios normales. Una variación menos de esto se podría utilizar una lectura dfuera de limites para ganwr el control en un camino mal-calculado, no hemos investigado esta variación más alla de esto.
¿Que es el eBPF JIT?

Para entender eBPF en Linux lo mejor es empezar con un sistema operativo diferente: FreeBSD. En 1993 el paper “The BSD Packet Filter – A New Architecture for User-level Packet Capture, McCanne & Jacobson” fue preentado en la Conferencia de Invierno USENIX de ese mismo año en San Diego, California, EEUU. Estaba escrito por Steven McCanne y Van Jacobson qye por aquel entonces trabajaban en el Lawrence Berkeley National Laboratory. En el paper, McCanne y Jacopson describen el filtro de paquetes BSD/BSD Packet Filter, o abreviado BPF, incluyendo su posicion en el kernel y su implementación como máquina virtual.

Lo que el BPF hacía diferente a sus predecesores, como el CMU/Stanford Packet Filter (CSPF), era el uso de un bien pensado modelo de memoria y entonces exponerlo a tra´ves de una eficiente máquina virtual dentro del kernel. De esta manera los filtros BPF pueden hacer filtro de tráfico de manera eficiente mientras mantienen las fronteras entre el código filtrado y el kernel.

En su paper, McCanne y Jacobson’s ofrecen el siguiente diagrama para ayudar a visualizarlo. Los más importante. BPF emplea un bífer entre el fultro y el espacio de suusario para evitar cambios de contexto caros entre el espacio del usuario y el espacio del kernel…

Pero BPF y eBPF son dos cosas distintas.

Cuando la versión 3.18 del kernel de Linux fue lanzada en Diciembre de 2014 integro la primera implementación del extended BPF (eBPF). Lo que eBPF ofrece, en resumen, es una máquina virtual en el kernel como el BPF,pero con unas pocas mejoras cruciales.En primer lugar eBPF es más efectivo que la máquina virtual BPF original gracias a la compilación JIT (Just in Time). Dos, esta diseñado para eventos de procesamiento general en el kernel. Permitiendo a los desarrolladores del kernel integral el eBPF en componentes en los que vean que encaja. Y trés, incluye un  llamado «maps». Esto le permite a un estado persistir en los eventos y ser agregado para usos que incluyen estadísticas y comportamiento consciente del contexto…

Con estas habilidades, los desarrolladores de kernel han utilizado rapidamente el eBPF en una variedad de kerneles del sistema. En menos de dos años y medio los usos han crecido para incluir monitorización de la red, manipulación del trafico de la red y monitorización del sistema. Así que los progemas del eBPF pueden ser utilizados para cambiar el tráfico de la red, medir el retraso de la lectura en disco o hacer seguimiento de las «cache misses» de la CPU.

Haciendo un rápido resumen, eBPF ha estado solo en el kernel de Linux desde 2014 pero ha encontrado su camino en diferentes usos en el kernel para un procesamiento eficiente de eventos. Gracias a los eBPF-maps, los programas escritos en eBPF pueden mantener su estado y añadir información a través de eventos y tener un comportamiento dinámico. Los usos del eBPF continuarían expandiendose gracias a su implementación minimalística y tener un rendimiento más rapido.

¿Es el eBPF el problema? Mejor volvamos a leer lo anterior:

Para ser capaz de utilizar este comportamiento para un ataque, el atacante necesita ser capaz de causar la ejecución de este código vulnerable en un contexto en un indice fuera de bordes. Para esto, el patrón vulnerable de código se ha de encontrar en código existente o tiene que existir un interprete o motor JIT que pueda generar el patrón de codigo vulnerable.

No es el eFB el problema sino el motor JIT del eFB.

En informática, la compilación en tiempo de ejecución (también conocida por sus siglas inglesas, JITjust-in-time), también conocida como traducción dinámica, es una técnica para mejorar el rendimiento de sistemas de programación que compilan a bytecode, consistente en traducir el bytecode a código máquina nativo en tiempo de ejecución. La compilación en tiempo de ejecución se construye a partir de dos ideas anteriores relacionadas con los entornos de ejecución: la compilación a bytecode y la compilación dinámica.

Y si, la compilacion en tiempo de ejecución no es realmente el problema sino el código generado del mismo, Spectre para funcionar bien necesita inyectar en su proceso el código malicioso y para ello solo necesitamos un JIT que lo genere para poderlo inyectar a continuación. ¿El gran problema? Existe otro compilador a tiempo de ejecución que se ha estandarizado llamado LLVM que basicamente es un JIT para lenguajes que no soportan por su naturaleza una máquina virtual de base, Apple utiliza LLVM para hacer independiente del conjunto de registros e instrucciones el procesador el código de iOS y Mac OS X. El simulador del iPhone en XCode utiliza LLVM por ejemplo.

El problema es que Apple lleva utilizando LLVM como sustitución al clásico GCC desde hace un tiempo… El motivo de utilizarlo tiene sentido si tenemos en cuenta que le otorga ciertas capacidades de lenguajes como Java y C# al lenguaje cuasi-propietario de Apple que es el Objective-C. Pero la LLVM es también la clave de las nuevas APIs gráficas… ¿Motivo? Con LLVM podemos generar directamente código de la API de alto nivel al set de registros e instrucciones de la gráfica en concreto eliminando la necesidad de un inteprete/controlador más complejo como ocurría hasta ahora. ¿Otros ejemplos de compiladores JIT altamente utilizados? Pues por la parte de Windows que tampoco se salva tenemos:

Pero hay que tener en cuenta que ni .NET, ni tampoco LLVM ni tampoco el JIT del eFB son la causa principal de Spectre sino que son posibles vías de inyección del código malicioso en Spectre para extraer al igual que con Meltdown los datos correspondientes al Kernel. Ahora bien… ¿Como afecta esto a las consolas de videojuegos? Obviamente no podemos ejecutar código no firmado en las consolas de manera directa. Por el momento para poder realizar el mismo proceso en una consola de videojuegos necesitamos tener un software firmado… ¿Pero que es lo que comprueba que el software este firmado? En el caso de PS4 y Xbox One es el Southbridge, un chip que se encarga de la gestión de E/S y por tanto se encarga de comprobar si los programas están firmados o no, si un programa no lo esta entonces no deja descargarlo a memoria, no deja ejecutarlo… Basicamente es un mecanismo de seguridad por hardware que tampoco es del todo fiable porque una vez se sabe como generar el código puede superar la seguridad y es por ello que existe un segundo nivel de seguridad. ¿Cual? Todos y cada uno de los binarios en vuestros juegos están compilados con un proceso de compresión especial cuya descompresión es realizada en ambos casos dentro del SoC por una unidad especial. Es decir, si enviamos código desde el BluRay y el Disco Duro que no este comprimido desde el SDK oficial a la hora de compilar lo que hace es aplicar la descompresión y destroza el código.

#2.2 Spectre en la Nube

Lo que es realmente terrorifico es la Variante 2 porque es lo que compromete por completo los llamados sistemas de computación en la nube y de computación distribuida, es decir, la ejecución de programas de manera remota.

Y es aquí donde entra lo importante y que no hemos comentado antes, el sistema de Memoria Virtual de los procesadores modernos no solamente le da acceso

Esta sección describe la teoría detrás de nuestra pieza de código para la Variante 2, cuando ejecutamos con privilegios de root dentro del kernel de la máquina virtual utilizando el virt-manager en la CPU Intel Haswell Xeon, con una distribución especifica del Kernel de Debian ejecutandose como huesped se puede leer la memoria del kernel del host a un ratio de 1500 bytes/segundo.

La idea básica para el ataque es tener como objetivo código víctima que contenga una rama indirecta cuya dirección objetivo es cargada en memoria y le da como dirección objetivo una que se encuentra en la memoria proncipal. Entonces, cuando la CPU alcanza la rama indirecta, no será capaz de saber el destino real del salto, no será capaz de calcularlo hasta que haya acabado de leer la lonea de cache en la CPU, lo cual toma cientos de ciclos, por lo que hay una ventaja de tiempo de tipicamente unos 100 ciclos en los que CPU estara ejecutando instruccionesde manera especulativa basandose en la predicción de saltos.

La Variante 1 se basa en aprovechar un bucle en fuera de limites, la Variante 2 se basa en aprovechar una rama indirecta. En la sección anterior ya he comentado como se puede aprovechar una construcción de Spectre desde un salto, lo realmente terrorifico es que en un ecosistema en el que se nos da parte de la potencia de un servidor remoto o simplemente nuestro sistema interactua con el servidor remoto es posible acceder a los datos de memoria de dicho servidor remoto. ¿Os acordáis de la debacle del PSN de hace unos años? Pues imaginaoslo ahora  a nivel general. Por ejemplo un usuario con conocimientos podría acceder a los servidores del PSN o del Xbox Live para:

  • Conseguir los datos de pago de los clientes si la empresa no es lo suficientemente precavida, así como información a sus cuentas privadas en el servició.
  • Poder descargar indiscriminadamente contenido adicional y juegos sin problema alguno directamente desde el servidor. Pensad que los juegos que os descargáis están asignados a una cuenta y cuando se descargan se comprimen y se genera el binario asignado a nuestra cuenta en concreto o mejor dicho a la consola asignada a dicha cuenta. El binario original ha de estar en el servidor por lo que se puede descargar en bruto y luego a partir de un software externo generar el binario legal para nuestra consola.
  • Dar permisos especiales a nuestra consola para poder descargar los juegos de manera directa que no hemos comprado. No debería poder hacerse de entrada pero…
  • Manipular los perfiles de jugador de manera directa otorgandose logros que no han conseguido de manera real.
  • Manipular partidas salvadas en la nube permitiendo trampas.

Hay que tener en cuenta que tanto el Xbox Live como el PSN son servicios que son P2P para el juego online pero que necesitan conectividad con los servidores para ciertas funcionalidades. ¿Cual será la defensa de ello? Pues dado que «La Industria» anda obsesionada con el «Todo Digital» con tal de eliminar la propiedad privada que reduzca el software al usufructo… Pues esto es un golpe en el fondo muy fuerte que por el momento no afecta a las consolas pero dado que Spectre es un problema general de todos y cada uno de los procesadores fuera de orden pues… Toda esta situación pone en tele de juicio toda la visión futura.

No solo en videojuegos sino también en servicios basados en Streaming como los de películas, ahora se sabe que es posible obtener cualquier dato de un servidor remoto. ¿Que tal un ataque a Movistar con tal de extraer todas las series en Español y Peliculas de Estreno? Pero lo más terrible es que coloca a los coches inteligentes en tela de juició y coloca todos los móviles dependientes de servicios remotos también en problemas.

#3 ¿Meltdown y Spectre en ARM?

La gente de ARM ha realizado una interesante tabla especificando en cual de los tres casos más uno adicional se ven sus procesadores afectados, por lo visto Spectre afecta mucho más a los ARM que Meltdown.

 

Como se puede ver, Spectre (Variantes 1 y 2) si que afectan a estos procesadores mientras que Meltdown en su definición estricta no les afecta excepto a unos tres procesadores, entre ellos esta el A57 y hemos de tener en cuenta que el A57 es la CPU de la Nintendo Switch.

La Variante 3A es descrita por ARM de la siguiente manera:

De la misma manera que con la Variante 3, en una pequeña implementación de los ARM, un procesador que especulativamente realiza una lectura a un registro del sistema que no es accesible en al actual nivel de excepción accedera al registro del sistema asociado (provéido con que es un registro que se puede leer sin efectos colaterales). Este acceso devolverá un valor especulativo en el registro que puede ser utilizado para cargar una serie de instrucciones especulativas. Si la especulacion no es correctan, entonces los resultados de la especulación son descartados por lo que hay una revelación a nivel arquitectrual de los datos del inaccesible registro del sistema. Sin embargo, en dichas implementaciones, los datos devueltos desde u registro inaccesible del sistema pueden ser utilizados para llevar más alla la especulación. Es en esta especulación que va más allá que puede ser explotada (por los canales laterales de obtención de datos como un cache+flush).

Es decir, una version modificada de Meltdown permitiría la obtención de datos en un A57… ¿Importa esto en una consola de videojuegos como por ejemplo Switch? Importa porque abre las puertas masivamente a…

Porque el problema en el caso de Spectre se puede solventar y aunque al igual que PS4 es vulnerable a Spectre por utilizar un SO que hace uso junto a Linux y macOS que utiliza los eBPF, pero el uso del eBPF se puede parchear a nivel de SO, Meltdown en cambio requiere cambios a nivel arquitectural añadiendo elementos que no permitan su ejecución y que por el momento van a ser añadidos a nivel de software en los Intel provocando una perdida enorme de rendimiento. ¿Como pueden los hackers reventar la seguridad de Switch con esto? Pues tan simple como la inyeccion de código a través de las falsas partidas salvadas, una vez que una partida salvada a ser cargada ejecute una excepción en el procesador que active la ejecución especulatoria entonces… Sera posible poder acceder a cosas como:

  • La clave de encriptación de los juegos.
  • El código entero del SO para poder crear una versión custom del mismo o poder ejecutar el código en una Shield TV.

¿Cuanto tardaran en hacerlo? No lo se, pero en el mundo de los smartphones no es problema desde el momento en que hace mucho tiempo la gente no utiliza ya móviles ni tablets con el Cortex A57 ni el Cortex A72 por lo que personalmente no creo que pierdan el tiempo pero en el caso de Nintendo si que afecta desde que una consola es un producto a largo plazo en el mercado donde la configuracion se mantiene intacta. En todo caso ARM sin quererlo le ha dicho a los hackers una metodología con la que poder crear una variación del Meltdown para ciertos núcleos ARM, el hecho de que un producto con éxito comercial pueda tener esa vulnerabilidad es un reto para los hackers.