vulnerabilities vulnerabilidad spectre quien que parche meltdown descubrio ataque and security assembly x86 cpu-architecture

security - spectre - vulnerabilidad meltdown que es



¿Qué es un retpoline y cómo funciona? (3)

Con el fin de mitigar la divulgación de memoria de kernel o de procesos cruzados (el ataque Spectre ), el kernel 1 de Linux se compilará con una nueva opción , -mindirect-branch=thunk-extern introducida en gcc para realizar llamadas indirectas a través de una llamada retpoline

Este parece ser un término recién inventado, ya que una búsqueda en Google solo muestra un uso muy reciente (generalmente todo en 2018).

¿Qué es una retpoline y cómo evita los recientes ataques de divulgación de información del núcleo?

1 Sin embargo, no es específico de Linux: parece que se usa una construcción similar o idéntica como parte de las estrategias de mitigación en otros sistemas operativos.


Esta pregunta se hizo hace un tiempo y merece una respuesta más reciente.

Resumen Ejecutivo

Las secuencias "Retpoline" son una construcción de software que permite aislar ramas indirectas de la ejecución especulativa. Esto se puede aplicar para proteger los binarios confidenciales (como el sistema operativo o las implementaciones de hipervisor) de los ataques de inyección de destino de rama contra sus ramas indirectas.

La palabra " ret poline " es un acrónimo de las palabras "return" y "trampoline", al igual que la mejora " rel poline " se acuñó de "call relativo" y "trampoline". Es una construcción de trampolín construida utilizando operaciones de retorno que también asegura figurativamente que cualquier ejecución especulativa asociada "rebotará" sin cesar.

Con el fin de mitigar la divulgación de memoria de kernel o de procesos cruzados (el ataque Spectre), el kernel de Linux [1] se compilará con una nueva opción, -mindirect-branch=thunk-extern introducida en gcc para realizar llamadas indirectas a través de un modo -llamado retpoline.

[1] Sin embargo, no es específico de Linux: parece que se utiliza una construcción similar o idéntica como parte de las estrategias de mitigación en otros sistemas operativos.

El uso de esta opción del compilador solo protege contra Spectre V2 en los procesadores afectados que tienen la actualización de microcódigo requerida para CVE-2017-5715. Funcionará en cualquier código (no solo en un núcleo), pero vale la pena atacar solo el código que contiene "secretos".

Este parece ser un término recién inventado, ya que una búsqueda en Google solo muestra un uso muy reciente (generalmente todo en 2018).

El compilador LLVM ha tenido un conmutador -mretpoline desde antes del 4 de enero de 2018 . Esa fecha es cuando la vulnerabilidad se informó públicamente por primera vez . GCC puso a disposición sus parches el 7 de enero de 2018.

La fecha CVE sugiere que la vulnerabilidad fue '' descubierta '' en 2017, pero afecta a algunos de los procesadores fabricados en las últimas dos décadas (por lo tanto, probablemente se descubrió hace mucho tiempo).

¿Qué es una retpoline y cómo evita los recientes ataques de divulgación de información del núcleo?

Primero, algunas definiciones:

  • Trampoline : en ocasiones, los trampolines de vectores de salto indirectos son ubicaciones de memoria que contienen direcciones que apuntan a interrumpir las rutinas de servicio, las rutinas de E / S, etc. La ejecución salta al trampolín y luego salta inmediatamente o rebota, de ahí el término trampolín. GCC ha apoyado tradicionalmente las funciones anidadas al crear un trampolín ejecutable en tiempo de ejecución cuando se toma la dirección de una función anidada. Este es un pequeño fragmento de código que normalmente reside en la pila, en el marco de la pila de la función que lo contiene. El trampolín carga el registro de la cadena estática y luego salta a la dirección real de la función anidada.

  • Thunk : un thunk es una subrutina utilizada para inyectar un cálculo adicional en otra subrutina. Los thunks se usan principalmente para retrasar un cálculo hasta que se necesite su resultado, o para insertar operaciones al principio o al final de la otra subrutina

  • Memoization : una función memorizada "recuerda" los resultados correspondientes a un conjunto de entradas específicas. Las llamadas posteriores con entradas recordadas devuelven el resultado recordado en lugar de volver a calcularlo, eliminando así el costo primario de una llamada con parámetros dados de todos menos la primera llamada realizada a la función con esos parámetros.

En términos generales, un retpoline es un trampolín con un retorno como un golpe , para " estropear " la memorización en el predictor indirecto de ramas.

Source : la retpoline incluye una instrucción PAUSE para Intel, pero una instrucción LFENCE es necesaria para AMD, ya que en ese procesador la instrucción PAUSE no es una instrucción de serialización, por lo que el bucle de pausa / jmp usará un exceso de energía, ya que se especula sobre la espera del retorno predecir mal el objetivo correcto.

Arstechnica tiene una explicación simple del problema:

"Cada procesador tiene un comportamiento arquitectónico (el comportamiento documentado que describe cómo funcionan las instrucciones y de los que dependen los programadores para escribir sus programas) y un comportamiento microarquitectónico (la forma en que se comporta una implementación real de la arquitectura). Estos pueden divergir de maneras sutiles. Por ejemplo, arquitectónicamente, un programa que carga un valor de una dirección particular en la memoria esperará hasta que se conozca la dirección antes de intentar realizar la carga. Sin embargo, microarquitecturalmente, el procesador podría intentar adivinar especulativamente la dirección para que pueda comenzar cargar el valor de la memoria (que es lento) incluso antes de que sea absolutamente seguro de qué dirección debe usar.

Si el procesador adivina mal, ignorará el valor adivinado y realizará la carga nuevamente, esta vez con la dirección correcta. El comportamiento arquitectónicamente definido se conserva así. Pero esa suposición errónea perturbará otras partes del procesador, en particular el contenido de la memoria caché. Estas perturbaciones microarquitectónicas se pueden detectar y medir midiendo el tiempo que se tarda en acceder a los datos que deberían (o no deberían) estar en la memoria caché, lo que permite que un programa malicioso haga inferencias sobre los valores almacenados en la memoria ".

Del documento de Intel: " Retpoline: una rama de mitigación de inyección objetivo " ( .PDF ):

"Una secuencia retpoline impide que la ejecución especulativa del procesador utilice el" predictor indirecto de ramificación "(una forma de predecir el flujo del programa) para especular a una dirección controlada por un exploit (satisfaciendo el elemento 4 de los cinco elementos de inyección de ramificación objetivo (variante 2 del espectro) ) explotar la composición mencionada anteriormente) ".

Tenga en cuenta que el elemento 4 es: "El exploit debe influir con éxito en esta rama indirecta para predecir y ejecutar especulativamente un gadget. Este gadget, elegido por el exploit, filtra los datos secretos a través de un canal lateral, generalmente mediante temporización de caché".


Un retpoline está diseñado para proteger contra la explotación de la inyección de objetivo de ramificación ( CVE-2017-5715 ). Este es un ataque donde se usa una instrucción indirecta de ramificación en el núcleo para forzar la ejecución especulativa de un fragmento arbitrario de código. El código elegido es un "gadget" que de alguna manera es útil para el atacante. Por ejemplo, se puede elegir el código para que filtre los datos del núcleo a través de cómo afecta a la memoria caché. La retpoline evita esta vulnerabilidad simplemente reemplazando todas las instrucciones indirectas de ramificación con una instrucción de retorno.

Creo que la clave de la retpoline es solo la parte "ret", que reemplaza la rama indirecta con una instrucción de retorno para que la CPU utilice el predictor de pila de retorno en lugar del predictor de rama explotable. Si en su lugar se usó una simple instrucción push y return, entonces el código que se ejecutaría especulativamente sería el código al que la función finalmente regresará de todos modos, no algún dispositivo útil para el atacante. El principal beneficio de la parte del trampolín parece ser mantener la pila de retorno, por lo que cuando la función realmente vuelve a su llamador, se predice correctamente.

La idea básica detrás de la inyección de objetivo de ramificación es simple. Aprovecha el hecho de que la CPU no registra la dirección completa del origen y el destino de las sucursales en sus búferes de destino de sucursal. Por lo tanto, el atacante puede llenar el búfer utilizando saltos en su propio espacio de direcciones que resultarán en aciertos de predicción cuando se ejecute un salto indirecto particular en el espacio de direcciones del núcleo.

Tenga en cuenta que retpoline no impide la divulgación de información del núcleo directamente, solo evita que se usen instrucciones indirectas de ramificación para ejecutar especulativamente un gadget que divulgaría información. Si el atacante puede encontrar algún otro medio para ejecutar especulativamente el dispositivo, entonces la retpoline no evita el ataque.

El documento Spectre por Paul Kocher, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, Michael Schwarz y Yuval Yarom ofrecen la siguiente descripción general de cómo se pueden explotar las ramas indirectas :

Explotación de ramas indirectas. A partir de la programación orientada al retorno (ROP), en este método el atacante elige un dispositivo del espacio de direcciones de la víctima e influye en la víctima para que ejecute el dispositivo de manera especulativa. A diferencia de ROP, el atacante no se basa en una vulnerabilidad en el código de la víctima. En su lugar, el atacante entrena el Branch Target Buffer (BTB) para predecir erróneamente una rama de una instrucción indirecta de rama a la dirección del dispositivo, lo que resulta en una ejecución especulativa del dispositivo. Si bien las instrucciones ejecutadas especulativamente se abandonan, sus efectos en el caché no se revierten. El gadget puede utilizar estos efectos para filtrar información confidencial. Mostramos cómo, con una selección cuidadosa de un dispositivo, este método puede usarse para leer la memoria arbitraria de la víctima.

Para entrenar mal el BTB, el atacante encuentra la dirección virtual del dispositivo en el espacio de direcciones de la víctima, luego realiza ramificaciones indirectas a esta dirección. Este entrenamiento se realiza desde el espacio de direcciones del atacante, y no importa qué reside en la dirección del dispositivo en el espacio de direcciones del atacante; todo lo que se requiere es que la sucursal utilizada para entrenar sucursales use la misma dirección virtual de destino. (De hecho, siempre que el atacante maneje las excepciones, el ataque puede funcionar incluso si no hay un código asignado en la dirección virtual del gadget en el espacio de direcciones del atacante). Tampoco hay necesidad de una coincidencia completa de la dirección de origen de la rama utilizada para la capacitación y la dirección de la rama objetivo. Por lo tanto, el atacante tiene una flexibilidad significativa en la configuración del entrenamiento.

Una entrada de blog titulada Lectura de memoria privilegiada con un canal lateral por el equipo del Proyecto Cero en Google proporciona otro ejemplo de cómo se puede utilizar la inyección de objetivo de rama para crear un exploit funcional.


El artículo mencionado por sgbj en los comentarios escritos por Paul Turner de Google explica lo siguiente con mucho más detalle, pero lo intentaré:

Hasta donde puedo reconstruir esto a partir de la información limitada en este momento, un retpoline es un trampolín de retorno que utiliza un bucle infinito que nunca se ejecuta para evitar que la CPU especule sobre el objetivo de un salto indirecto.

El enfoque básico se puede ver en la rama del núcleo de Andi Kleen que aborda este problema:

Presenta la nueva llamada __x86.indirect_thunk que carga el destino de la llamada cuya dirección de memoria (que llamaré ADDR ) se almacena en la parte superior de la pila y ejecuta el salto usando la instrucción RET . El thunk en sí mismo se llama usando la macro NOSPEC_JMP/CALL , que se usó para reemplazar muchas (y no todas) llamadas indirectas y saltos. La macro simplemente coloca el destino de la llamada en la pila y establece la dirección de retorno correctamente, si es necesario (tenga en cuenta el flujo de control no lineal):

.macro NOSPEC_CALL target jmp 1221f /* jumps to the end of the macro */ 1222: push /target /* pushes ADDR to the stack */ jmp __x86.indirect_thunk /* executes the indirect jump */ 1221: call 1222b /* pushes the return address to the stack */ .endm

La ubicación de la call al final es necesaria para que cuando finalice la llamada indirecta, el flujo de control continúe detrás del uso de la macro NOSPEC_CALL , de modo que pueda usarse en lugar de una call normal

El thunk en sí se ve de la siguiente manera:

call retpoline_call_target 2: lfence /* stop speculation */ jmp 2b retpoline_call_target: lea 8(%rsp), %rsp ret

El flujo de control puede ser un poco confuso aquí, así que déjenme aclarar:

  • call empuja el puntero de instrucción actual (etiqueta 2) a la pila.
  • lea agrega 8 al puntero de la pila , descartando de manera efectiva la última palabra cuádruple, que es la última dirección de retorno (a la etiqueta 2). Después de esto, la parte superior de la pila apunta a la dirección de retorno real ADDR nuevamente.
  • ret salta a *ADDR y restablece el puntero de la pila al comienzo de la pila de llamadas.

Al final, todo este comportamiento es prácticamente equivalente a saltar directamente a *ADDR . El único beneficio que obtenemos es que el predictor de rama utilizado para las declaraciones de retorno (Return Stack Buffer, RSB), al ejecutar la instrucción de call , supone que la declaración ret correspondiente saltará a la etiqueta 2.

La parte posterior a la etiqueta 2 en realidad nunca se ejecuta, es simplemente un bucle infinito que en teoría llenaría la tubería de instrucciones con instrucciones JMP . Al usar LFENCE , PAUSE o, en general, una instrucción que hace que el LFENCE instrucciones se detenga, la CPU no desperdicia energía y tiempo en esta ejecución especulativa. Esto se debe a que en caso de que la llamada a retpoline_call_target regrese normalmente, LFENCE sería la siguiente instrucción que se ejecutará. Esto también es lo que predecirá el predictor de sucursal en función de la dirección de devolución original (la etiqueta 2)

Para citar del manual de arquitectura de Intel:

Las instrucciones que siguen a un LFENCE se pueden recuperar de la memoria antes del LFENCE, pero no se ejecutarán hasta que el LFENCE se complete.

Sin embargo, tenga en cuenta que la especificación nunca menciona que LFENCE y PAUSE hacen que la tubería se detenga, por lo que estoy leyendo un poco entre líneas aquí.

Ahora volvamos a su pregunta original: la divulgación de información de la memoria del núcleo es posible debido a la combinación de dos ideas:

  • Aunque la ejecución especulativa debería estar libre de efectos secundarios cuando la especulación era incorrecta, la ejecución especulativa todavía afecta la jerarquía de caché . Esto significa que cuando una carga de memoria se ejecuta especulativamente, aún puede haber provocado el desalojo de una línea de caché. Este cambio en la jerarquía de caché se puede identificar midiendo cuidadosamente el tiempo de acceso a la memoria que se asigna al mismo conjunto de caché.
    Incluso puede perder algunos bits de memoria arbitraria cuando la dirección de origen de la lectura de la memoria fue leída de la memoria del núcleo.

  • El predictor de ramificación indirecta de las CPU de Intel solo usa los 12 bits más bajos de la instrucción de origen, por lo que es fácil envenenar todos los 2 ^ 12 posibles historiales de predicción con direcciones de memoria controladas por el usuario. Estos pueden, entonces, cuando se predice el salto indirecto dentro del núcleo, ejecutarse especulativamente con privilegios del núcleo. Usando el canal lateral de sincronización de caché, puede perder memoria arbitraria del núcleo.

ACTUALIZACIÓN: En la lista de correo del kernel , hay una discusión en curso que me lleva a creer que las retpolines no mitigan por completo los problemas de predicción de la rama, como cuando el Buffer de apilamiento de retorno (RSB) se ejecuta vacío, las arquitecturas Intel más recientes (Skylake +) retroceden al búfer de destino de rama vulnerable (BTB):

Retpoline, como estrategia de mitigación, intercambia ramas indirectas por retornos, para evitar el uso de predicciones que provienen del BTB, ya que pueden ser envenenadas por un atacante. El problema con Skylake + es que un desbordamiento de RSB recurre al uso de una predicción BTB, lo que permite al atacante tomar el control de la especulación.