¿Por qué las CPU funcionan de manera especulativa con los resultados de búsquedas de memoria prohibidas?

6

Por lo que sé, los ataques Meltdown y Specter explotan el hecho de que algunos procesadores modernos, cuando se les da algo como:

if (x < arr1[y])
  z = arr2[arr3[x]*256];

a veces puede obtener el valor de arr2[arr3[x] * 256] antes de que hayan determinado si x es menor que arr1[y] , y puede hacer esto sin tener en cuenta si el código tiene permiso adecuado para acceder a arr3[x] . La parte en cursiva es el sine qua non de la vulnerabilidad: si el procesador solo fuera a buscar arr2[arr3[x] * 256] en los casos en que el acceso a arr3[x] fuera permisible, sería imposible explotar un a arr2[___ * 256] usando valores arr3[x] ilegítimos porque no habría ninguno.

Tiene sentido que incluso si arr3[x] no es válido, el procesador no pueda interceptarse a menos que o hasta que determine que x sea menor que arr1[y] . Lo que no entiendo es por qué una recuperación especulativa de una dirección no válida no debería hacer que la CPU abandone la ruta de ejecución especulativa actual. Pensaría que en casi todos los escenarios realistas ocurriría una de dos cosas:

  1. La predicción de bifurcación que condujo a la ejecución especulativa resultó ser incorrecta, en cuyo caso será necesario descartar cualquier trabajo que pueda realizarse con el valor obtenido de forma especulativa.

  2. La predicción de bifurcación que condujo a la ejecución especulativa resulta ser correcta, en cuyo caso la ejecución debería interceptarse en el acceso no válido, y el trabajo que podría realizarse con el valor obtenido de forma especulativa (antes de ejecutar la captura) necesita ser descartado.

¿Hay algún escenario realista en el que el trabajo especulativo que sigue una búsqueda especulativa desde una dirección ilegítima pueda resultar útil? Si no, ¿qué ventaja tiene permitir que la ejecución especulativa continúe más allá de tales capturas? Si una recuperación no válida hará que la CPU abandone la línea actual de ejecución especulativa, evitaría la necesidad de realizar un seguimiento de las trampas especulativas pendientes.

    
pregunta supercat 05.01.2018 - 00:27
fuente

2 respuestas

3

Considere el siguiente código:

mprotect(arr3, sizeof(arr3), PROT_NONE);
...
mprotect(arr3, sizeof(arr3), PROT_READ);
z = arr2[arr3[x]*256];

En algún momento, arr3 se vuelve legible por el proceso. Si la asignación (bueno, en realidad la desreferencia de arr3) ya se ha cargado antes de que se complete el mprotect, ¿qué comportamiento esperaría? No esperaría una trampa, ya que es válida en el momento.

Básicamente, no sabes cuál es el estado de la memoria en arr3 hasta la instrucción antes de que se retire, momento en el que la tubería se habrá ejecutado mucho más allá de ese punto.

Supongo que podría "capturar de forma especulativa" y volver a ejecutar si los permisos de memoria cambian mientras tanto, pero aparentemente eso no es lo que se hace hoy. (Aunque imagino que sería un impacto de rendimiento mucho menor que las correcciones del software KPTI).

    
respondido por el David 05.01.2018 - 01:52
fuente
0

Obviamente, es una mala idea hacer algo con el resultado de una carga especulativa de una ubicación de memoria que el código no puede leer. Pero no puede simplemente prohibir el uso de datos de una ubicación de memoria que no se le permitió leer, porque a menudo no lo sabe (todavía). Tendrías que prohibir el uso de datos desde una ubicación de memoria en la que no hayas descubierto todavía si puedes leerlo o no.

Desafortunadamente, los datos de otros procesos pueden estar realmente en su caché L1. Por lo tanto, si lee desde el caché L1 (que es muy rápido), el procesador no sabe aún si se le permitió acceder a los datos o no. Así que tendrías que esperar básicamente en cada acceso a la memoria hasta que se verifiquen los permisos, lo que ralentizaría considerablemente las cosas. Bueno, al menos con la forma en que Intel diseñó sus procesadores. Como los procesadores de AMD no se ejecutan a la velocidad de un caracol, obviamente están haciendo algo diferente.

Un método (que requiere un cambio sustancial en el procesador) sería asegurarse de que aunque los datos de otro proceso puedan estar en su caché L1, dichos datos siempre generarán una falla de caché (si cambia los procesos, entonces los mismos datos mágicamente ya no produciría fallas de caché). De esa manera, podría usar los datos de la memoria caché L1 de forma segura y, si no está en la memoria caché L1, es posible que tenga tiempo suficiente para verificar los permisos antes de que lleguen los datos.

Otro método sería detener la ejecución especulativa cuando sucede algo que podría causar cambios detectables. Por ejemplo, después de una lectura especulativa, podría continuar hasta que encuentre una operación que cambiaría el caché de cualquier manera.

    
respondido por el gnasher729 07.01.2018 - 21:02
fuente

Lea otras preguntas en las etiquetas