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 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.
security
assembly
x86
cpu-architecture
BeeOnRope
fuente
fuente
gcc
apunta de esa manera! No reconocí lkml.org/lkml/2018/1/3/780 como en el sitio de la lista de correo del kernel de Linux, ni siquiera una vez que miré allí (y recibí una instantánea ya que estaba fuera de línea).Respuestas:
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, una 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:
Introduce la nueva
__x86.indirect_thunk
llamada 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 laRET
instrucción. El thunk en sí mismo se llama utilizando la macro NOSPEC_JMP / CALL , que se utilizó para reemplazar muchas (si no todas) las 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 (observe el flujo de control no lineal):La colocación de
call
al final es necesaria para que cuando finalice la llamada indirecta, el flujo de control continúe detrás del uso de laNOSPEC_CALL
macro, por lo que se puede usar en lugar de un comando regular.call
El thunk en sí se ve de la siguiente manera:
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 efectivamente 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*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 lacall
instrucción, supone que laret
declaración 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
JMP
instrucciones con instrucciones. Al usarLFENCE
,PAUSE
o más generalmente, una instrucción que hace que el canal de 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 retorno original (la etiqueta 2)Para citar del manual de arquitectura de Intel:
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 indirecto de ramificación de las CPU Intel solo usa los 12 bits más bajos de la instrucción fuente, 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 de 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 pila de retorno (RSB) se ejecuta vacío, las arquitecturas Intel más recientes (Skylake +) retroceden al búfer de destino de rama vulnerable (BTB)
fuente
push
/ret
que no desequilibra la pila de predictores de dirección de retorno. Hay un error de predicción (irlfence
antes de que se use la dirección de retorno real), pero usar unacall
modificación + lorsp
equilibróret
.push
/ret
(en mi último comentario). re: su edición: el desbordamiento de RSB debería ser imposible porque la retpoline incluye acall
. Si la prevención del núcleo hizo un cambio de contexto allí, reanudaríamos la ejecución con el RSB preparado desdecall
el programador. Pero tal vez un controlador de interrupciones podría terminar con suficientesret
s para vaciar el RSB.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 use 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 regresa a su llamador, esto se predice correctamente.
La idea básica detrás de la inyección de objetivo de rama 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 Specter Attacks: Explotando la ejecución especulativa 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 :
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.
fuente
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 "de retorno" y "trampolín", al igual que la mejora " rel poline " fue acuñado de "llamada relativa" y "cama elástica". Es una construcción de trampolín construida usando operaciones de retorno que también asegura figurativamente que cualquier ejecución especulativa asociada "rebotará" sin cesar.
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. Lo hará ' trabajo ' en cualquier código (no sólo un núcleo), pero sólo el código que contiene "secretos" vale atacante.
El compilador LLVM ha tenido un
-mretpoline
cambio 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 se ' descubrió ' 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).
Primero, algunas definiciones:
Trampolín : 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. 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 algún 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.
Fuente : 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 de retorno predecir erróneamente el objetivo correcto.
Arstechnica tiene una explicación simple del problema:
Del documento de Intel: " Retpoline: una rama de mitigación de inyección objetivo " ( .PDF ):
fuente