Autores:
(1) Zane Weissman, Instituto Politécnico de Worcester, Worcester, MA, EE. UU. {[email protected]};
(2) Thomas Eisenbarth, Universidad de Lübeck Lübeck, SH, Alemania {[email protected]};
(3) Thore Tiemann, Universidad de Lübeck Lübeck, SH, Alemania {[email protected]};
(4) Berk Sunar, Instituto Politécnico de Worcester Worcester, MA, EE. UU. {[email protected]}.
La máquina virtual basada en kernel (KVM) de Linux [29] proporciona una abstracción de las funciones de virtualización asistida por hardware como Intel VT-x o AMD-V que están disponibles en las CPU modernas. Para admitir la ejecución casi nativa, se agrega un modo de invitado al kernel de Linux además del modo de kernel y el modo de usuario existentes. Si está en el modo invitado de Linux, KVM hace que el hardware entre en el modo de virtualización de hardware que replica los privilegios de los anillos 0 y 3.[1]
Con KVM, la virtualización de E/S se realiza principalmente en el espacio del usuario mediante el proceso que creó la VM, denominado VMM o hipervisor, en contraste con los hipervisores anteriores que normalmente requerían un proceso de hipervisor separado [41]. Un hipervisor KVM proporciona a cada VM invitada su propia región de memoria que está separada de la región de memoria del proceso que creó el invitado. Esto es cierto tanto para los invitados creados desde el espacio del kernel como desde el espacio del usuario. Cada VM se asigna a un proceso en el host de Linux y cada CPU virtual asignada al invitado es un subproceso en ese proceso del host. El proceso de hipervisor del espacio de usuario de la VM realiza llamadas al sistema KVM solo cuando se requiere una ejecución privilegiada, minimizando el cambio de contexto y reduciendo la VM a la superficie de ataque del kernel. Además de impulsar mejoras de rendimiento en todo tipo de aplicaciones, este diseño ha permitido el desarrollo de hipervisores livianos que son especialmente útiles para proteger programas individuales y soportar entornos de nube donde se ejecutan muchas máquinas virtuales al mismo tiempo.
Un modelo cada vez más popular de computación en la nube es la computación sin servidor, en la que el CSP gestiona la escalabilidad y la disponibilidad de los servidores que ejecutan el código del usuario. Una implementación de la informática sin servidor se llama función como servicio (FaaS). En este modelo, un usuario de la nube define funciones que se llaman según sea necesario a través de la interfaz de programación de aplicaciones (API) del proveedor de servicios (de ahí el nombre "función como servicio") y el CSP gestiona la asignación de recursos en el servidor que ejecuta la función del usuario (de ahí el nombre “computación sin servidor”: el usuario no administra el servidor). De manera similar, la informática de contenedor como servicio (CaaS) ejecuta contenedores, paquetes de tiempo de ejecución portátiles, bajo demanda. La gestión centralizada de servidores de FaaS y CaaS es económicamente atractiva tanto para los CSP como para los usuarios. El CSP puede gestionar las cargas de trabajo de sus usuarios como quiera, optimizar para lograr un costo operativo mínimo e implementar precios flexibles en los que los usuarios pagan por el tiempo de ejecución que utilizan. El usuario no necesita preocuparse por el diseño o la gestión de la infraestructura del servidor, por lo que reduce los costos de desarrollo y subcontrata los costos de mantenimiento al CSP a un ritmo relativamente pequeño y predecible.
Los proveedores de FaaS y CaaS utilizan una variedad de sistemas para administrar funciones y contenedores en ejecución. Los sistemas de contenedores como Docker, Podman y LXD brindan una forma conveniente y liviana de empaquetar y ejecutar aplicaciones en espacio aislado en cualquier entorno. Sin embargo, en comparación con las máquinas virtuales utilizadas para muchas formas más tradicionales de computación en la nube, los contenedores ofrecen menos aislamiento y, por lo tanto, menos seguridad. En los últimos años, los principales CSP han introducido microVM que respaldan los contenedores tradicionales con virtualización liviana para mayor seguridad. [1, 55] La eficiencia de la virtualización de hardware con KVM y el diseño liviano de microVM significa que el código en sistemas virtualizados, en contenedores o similares a contenedores puede ejecutarse casi tan rápido como el código no virtualizado y con una sobrecarga comparable a la de un contenedor tradicional.
Firecracker [1] es una microVM desarrollada por AWS para aislar cada una de las cargas de trabajo de AWS Lambda FaaS y AWS Fargate CaaS en una VM separada. Solo admite invitados Linux en hosts x86 o ARM Linux-KVM y proporciona una cantidad limitada de dispositivos que están disponibles para los sistemas invitados. Estas limitaciones permiten que Firecracker sea muy liviano en el tamaño de su código base y en la sobrecarga de memoria para una VM en ejecución, además de ser muy rápido de iniciar o apagar. Además, el uso de KVM aligera los requisitos de Firecracker, ya que algunas funciones de virtualización son manejadas por llamadas al sistema del kernel y el sistema operativo host administra las VM como procesos estándar. Debido a su pequeña base de código escrita en Rust, se supone que Firecracker es muy seguro, aunque se han identificado fallas de seguridad en versiones anteriores (consulte CVE-2019-18960). Curiosamente, el documento técnico de Firecracker declara que los ataques a la microarquitectura están dentro del alcance de su modelo de atacante [1], pero carece de un análisis de seguridad detallado o contramedidas especiales contra los ataques a la microarquitectura más allá de las recomendaciones comunes de configuración segura del sistema para el kernel huésped y host. La documentación de Firecracker proporciona recomendaciones de seguridad del sistema [8] que incluyen una lista específica de contramedidas, que cubrimos en la sección 2.6.1.
En 2018, el ataque Meltdown [32] demostró que los datos a los que se accedía de forma especulativa podían filtrarse a través de los límites de seguridad codificándolos en un canal lateral de caché. Esto pronto condujo a toda una clase de ataques similares, conocidos como muestreo de datos de microarquitectura (MDS), incluidos Fallout [14], Rogue In-flight Data Load (RIDL) [50], TSX Asynchronous Abort (TAA) [50] y Carga zombi [46]. Todos estos ataques siguen el mismo patrón general para explotar la ejecución especulativa:
(1) La víctima ejecuta un programa que maneja datos secretos y los datos secretos pasan a través de un caché o un búfer de CPU.
(2) El atacante ejecuta una instrucción elegida específicamente que hará que la CPU prediga erróneamente que se necesitarán los datos secretos. La CPU envía los datos secretos a las instrucciones del atacante.
(3) Los datos secretos reenviados se utilizan como índice de una memoria leída en una matriz a la que el atacante está autorizado a acceder, lo que provoca que una línea particular de esa matriz se almacene en caché.
(4) La CPU termina de verificar los datos y decide que los datos secretos se reenviaron incorrectamente y revierte el estado de ejecución al anterior al reenvío, pero el estado de la memoria caché no se revierte. (5) El atacante explora toda la matriz para ver qué línea se almacenó en caché; el índice de esa línea es el valor de los datos secretos.
La vulnerabilidad Meltdown original tenía como objetivo el reenvío de caché y permitía la extracción de datos de esta manera desde cualquier dirección de memoria que estuviera presente en el caché. Los ataques MDS se dirigen a buffers más pequeños y específicos en la microarquitectura del núcleo, por lo que constituyen una clase de ataques relacionados pero distintos que se mitigan de una manera significativamente diferente. Mientras que Meltdown se dirige a la memoria principal que se actualiza con relativa poca frecuencia y se comparte entre todos los núcleos, subprocesos y procesos, los ataques MDS tienden a apuntar a buffers que son locales de los núcleos (aunque a veces se comparten entre subprocesos) y se actualizan con más frecuencia durante la ejecución.
2.4.1 Variantes básicas de MDS . La Figura 1 muestra las principales rutas de ataque MDS conocidas en CPU Intel y los nombres dados a las diferentes variantes por Intel y por los investigadores que las informaron. En términos más generales, Intel clasifica las vulnerabilidades MDS en sus CPU según el búfer específico desde el cual se reenvían los datos de manera especulativa, ya que estos búferes tienden a usarse para varias operaciones diferentes. Las vulnerabilidades de RIDL MDS se pueden clasificar como muestreo de datos del puerto de carga de microarquitectura (MLPDS), para variantes que se filtran desde el puerto de carga de la CPU, y muestreo de datos del búfer de relleno de microarquitectura (MFBDS), para variantes que se filtran desde el LFB de la CPU. En la misma línea, Intel llama a la vulnerabilidad Fallout Microarchitectural Store Buffer Data Sampling (MSBDS), ya que implica una fuga del buffer de la tienda. Vector Register Sampling (VRS) es una variante de MSBDS que apunta a datos manejados por operaciones vectoriales a medida que pasan a través del búfer de almacenamiento. VERW bypass explota un error en el
Correcciones de microcódigo para MFBDS que cargan datos obsoletos y potencialmente secretos en el LFB. El mecanismo básico de fuga es el mismo y el bypass VERW puede considerarse un caso especial de MFBDS. El muestreo de desalojo de datos L1 (L1DES) es otro caso especial de MFBDS, donde los datos que se desalojan de la caché de datos L1 pasan a través del LFB y se vuelven vulnerables a un ataque MDS. En particular, L1DES es un caso en el que el atacante puede desencadenar la presencia de datos secretos en la CPU (desalojándolos), mientras que otros ataques MDS dependen directamente del proceso de la víctima que accede a los datos secretos para llevarlos al búfer correcto de la CPU.
2.4.2 Medusa. Medusa [37] es una categoría de ataques MDS clasificados por Intel como variantes de MLPDS [25]. Las vulnerabilidades de Medusa explotan los algoritmos imperfectos de coincidencia de patrones utilizados para combinar especulativamente almacenes en el búfer de combinación de escritura (WC) de los procesadores Intel. Intel considera que el búfer WC es parte del puerto de carga, por lo que clasifica esta vulnerabilidad como un caso de MLPDS. Hay tres variantes conocidas de Medusa, cada una de las cuales explota una característica diferente del búfer de combinación de escritura para provocar una fuga especulativa:
Indexación de caché: una carga con errores se combina especulativamente con una carga anterior con un desplazamiento de línea de caché coincidente.
Reenvío de almacenamiento a carga no alineado: un almacenamiento válido seguido de una carga dependiente que desencadena una falla de memoria desalineada provoca que se reenvíen datos aleatorios del WC.
Shadow REP MOV : una instrucción REP MOV defectuosa seguida de una carga dependiente filtra los datos de un REP MOV diferente.
2.4.3 Aborto Asíncrono TSX. La vulnerabilidad de hardware TSX Asynchronous Abort (TAA) [24] proporciona un mecanismo de especulación diferente para llevar a cabo un ataque MDS. Mientras que los ataques MDS estándar acceden a datos restringidos con una ejecución especulada estándar, TAA utiliza una transacción de memoria atómica implementada por TSX. Cuando una transacción de memoria atómica encuentra un aborto asíncrono, por ejemplo porque otro proceso lee una línea de caché marcada para su uso por la transacción o porque la transacción encuentra un error, todas las operaciones de la transacción se revierten al estado arquitectónico anterior a que comenzara la transacción. Sin embargo, durante esta reversión, las instrucciones dentro de la transacción que ya comenzaron a ejecutarse pueden continuar con la ejecución especulativa, como en los pasos (2) y (3) de otros ataques MDS. TAA afecta a todos los procesadores Intel que admiten TSX, y en el caso de ciertos procesadores más nuevos que no se ven afectados por otros ataques MDS, se deben implementar mitigaciones de MDS o mitigaciones específicas de TAA (como deshabilitar TSX) en el software para proteger contra TAA [24].
2.4.4 Mitigaciones. Aunque las vulnerabilidades de clase Meltdown y MDS explotan operaciones de microarquitectura de bajo nivel , se pueden mitigar con parches de firmware de microcódigo en las CPU más vulnerables.
Aislamiento de la tabla de páginas. Históricamente, las tablas de páginas del kernel se han incluido en las tablas de páginas de procesos a nivel de usuario para que un proceso a nivel de usuario pueda realizar una llamada del sistema al kernel con una sobrecarga mínima. El aislamiento de la tabla de páginas (propuesto por primera vez por Gruss et al. como KAISER [19]) asigna sólo la memoria mínima necesaria del núcleo a la tabla de páginas del usuario e introduce una segunda tabla de páginas a la que sólo puede acceder el núcleo. Cuando el proceso del usuario no puede acceder a la tabla de páginas del kernel, los accesos a toda la memoria del kernel, excepto a una pequeña y específicamente elegida, se detienen antes de que alcancen las cachés de nivel inferior donde comienza un ataque Meltdown.
Sobrescritura del búfer. Los ataques MDS que tienen como objetivo los buffers de la CPU en el núcleo requieren una defensa de nivel inferior y más específica. Intel introdujo una actualización de microcódigo que sobrescribe los buffers vulnerables cuando se vacía la caché de datos de primer nivel (L1d) (un objetivo común de los ataques de canal lateral de temporización de caché) o se ejecuta la instrucción VERW [25]. Luego, el kernel puede protegerse contra ataques MDS activando una sobrescritura del búfer al cambiar a un proceso que no es de confianza.
La mitigación de sobrescritura del búfer apunta a los ataques MDS en su origen, pero es imperfecta, por decir lo menos. Los procesos siguen siendo vulnerables a ataques de subprocesos que se ejecutan simultáneamente en el mismo núcleo cuando SMT está habilitado (ya que ambos subprocesos comparten búferes vulnerables sin que el proceso activo realmente cambie en ninguno de los subprocesos). Además, poco después de la actualización del microcódigo de sobrescritura del búfer original, el equipo de RIDL descubrió que en algunas CPU Skylake, los buffers se sobrescribieron con datos obsoletos y potencialmente confidenciales [50], y permanecieron vulnerables incluso con las mitigaciones habilitadas y SMT deshabilitado. Otros procesadores son vulnerables a ataques TAA pero no a ataques MDS no TAA, y no recibieron una actualización de microcódigo de sobrescritura del búfer y, como tales, requieren que TSX se desactive por completo para evitar ataques MDS [20, 24].
2.5 espectro
En 2018, Jan Horn y Paul Kocher [30] informaron de forma independiente sobre las primeras variantes de Spectre. Desde entonces, se han descubierto muchas variantes diferentes de Spectre [22, 30, 31, 33] y subvariantes [10, 13, 16, 28, 52]. Los ataques Spectre hacen que la CPU acceda de forma especulativa a memoria que es arquitectónicamente inaccesible y filtre los datos al estado arquitectónico. Por lo tanto, todas las variantes de Spectre constan de tres componentes [27]:
El primer componente es el dispositivo Spectre que se ejecuta de forma especulativa. Las variantes de espectro suelen estar separadas por la fuente de la predicción errónea que explotan. El resultado de una rama directa condicional, por ejemplo, se predice mediante la tabla de historial de patrones (PHT). Las predicciones erróneas del PHT pueden llevar a una omisión de verificación de límites especulativos para las instrucciones de carga y almacenamiento [13, 28, 30]. El objetivo de rama de un salto indirecto lo predice el Branch Target Buffer (BTB). Si un atacante puede influir en el resultado de una predicción errónea del BTB, entonces son posibles ataques de programación especulativos orientados al retorno [10, 13, 16, 30]. Lo mismo ocurre con las predicciones realizadas por el Return Stack Buffer (RSB) que predice direcciones de retorno durante la ejecución de instrucciones de retorno [13, 31, 33]. Resultados recientes mostraron que algunas CPU modernas utilizan el BTB para sus predicciones de direcciones de retorno si el RSB tiene un desbordamiento insuficiente [52]. Otra fuente de ataques de Spectre es la predicción de dependencias de almacenamiento a carga. Si se predice erróneamente que una carga no depende de un almacén anterior, se ejecuta especulativamente en datos obsoletos, lo que puede llevar a una omisión especulativa del almacén [22]. Todos estos dispositivos no son explotables de forma predeterminada, sino que dependen de los otros dos componentes que se analizan ahora.
El segundo componente es cómo un atacante controla las entradas a los dispositivos antes mencionados. Los atacantes pueden definir valores de entrada de dispositivos directamente a través de la entrada del usuario, contenidos de archivos, paquetes de red u otros mecanismos arquitectónicos. Por otro lado, los atacantes pueden inyectar datos en el dispositivo de forma transitoria mediante inyección de valor de carga [12] o inyección de valor de punto flotante [42]. Los atacantes pueden controlar con éxito las entradas de los dispositivos si pueden influir en los datos o instrucciones a los que se accede o ejecuta durante la ventana de especulación.
El tercer componente es el canal encubierto que se utiliza para transferir el estado microarquitectónico especulativo a un estado arquitectónico y, por lo tanto, exfiltrar los datos a los que se accede especulativamente a un entorno persistente. Los canales encubiertos de caché [39, 40, 54] son aplicables si el código de la víctima realiza un acceso transitorio a la memoria dependiendo de datos secretos a los que se accedió especulativamente [30]. Si se accede a un secreto de forma especulativa y se carga en un buffer interno, un atacante puede confiar en un canal basado en MDS [14, 46, 50] para transferir transitoriamente los datos exfiltrados al subproceso del atacante donde los datos se transfieren al sistema arquitectónico. estado a través de, por ejemplo, un canal encubierto de caché. Por último, pero no menos importante, si la víctima ejecuta código dependiendo de datos secretos, el atacante puede conocer el secreto observando la contención de puertos [3, 11, 18, 43, 44].
2.5.1 Mitigaciones. Se desarrollaron muchas contramedidas para mitigar las diversas variantes de Spectre. Una variante específica de Spectre se desactiva efectivamente si se elimina uno de los tres componentes requeridos. Es poco probable que un atacante sin control sobre las entradas de los dispositivos Spectre lance un ataque con éxito. Lo mismo ocurre si no se dispone de un canal encubierto para transformar el estado especulativo en un estado arquitectónico. Pero como esto suele ser difícil de garantizar, las contramedidas de Spectre se centran principalmente en detener las predicciones erróneas. Insertar instrucciones de valla antes de secciones de código críticas deshabilita la ejecución especulativa más allá de este punto y, por lo tanto, puede usarse como una contramedida genérica. Pero debido a su alto rendimiento, se desarrollaron contramedidas más específicas. Las contramedidas de Spectre-BTB incluyen Retpoline [48] y actualizaciones de microcódigo como IBRS, STIBP o IBPB [23]. Spectre-RSB y Spectre-BTB-via-RSB se pueden mitigar llenando el RSB con valores para sobrescribir entradas maliciosas y evitar que el RSB se desborde o instalando actualizaciones de microcódigo IBRS. Spectre-STL puede mitigarse mediante la actualización del microcódigo SSBD [23]. Otra opción drástica para evitar que un atacante altere los buffers de predicción de ramas compartidos es desactivar SMT. La desactivación de SMT divide de manera efectiva los recursos de hardware de predicción de sucursales entre inquilinos simultáneos a costa de una pérdida significativa de rendimiento.
Firecracker está diseñado específicamente para aplicaciones de contenedores y sin servidor [1] y actualmente lo utilizan Fargate CaaS y Lambda FaaS de AWS. En ambos modelos de servicio, Firecracker es el sistema de aislamiento principal que admite cada tarea individual de Fargate o evento Lambda. Ambos modelos de servicio también están diseñados para ejecutar una gran cantidad de tareas relativamente pequeñas y de corta duración. AWS detalla los requisitos de diseño para el sistema de aislamiento que eventualmente se convirtió en Firecracker de la siguiente manera:
Aislamiento : debe ser seguro que se ejecuten múltiples funciones en el mismo hardware, protegido contra escalada de privilegios, divulgación de información, canales encubiertos y otros riesgos.
Gastos generales y densidad: debe ser posible ejecutar miles de funciones en una sola máquina, con un desperdicio mínimo.
Rendimiento: las funciones deben funcionar de manera similar a la ejecución nativa. El rendimiento también debe ser coherente y estar aislado del comportamiento de los vecinos en el mismo hardware.
Compatibilidad : Lambda permite que funciones contengan bibliotecas y binarios de Linux arbitrarios. Estos deben ser compatibles sin cambios de código ni recompilación.
Cambio rápido: debe ser posible iniciar nuevas funciones y limpiar funciones antiguas rápidamente.
Asignación suave: debe ser posible comprometer en exceso la CPU, la memoria y otros recursos, de modo que cada función consuma solo los recursos que necesita, no los recursos a los que tiene derecho. [1]
Estamos particularmente interesados en el requisito de aislamiento y enfatizamos que los ataques de microarquitectura se declaran dentro del alcance del modelo de amenaza Firecracker. La página de “diseño” en el repositorio público Firecracker Git de AWS detalla el modelo de aislamiento y proporciona un diagrama útil que reproducimos en la Figura 2. Este diagrama se refiere principalmente a la protección contra la escalada de privilegios. La capa más externa de protección es el jailer, que utiliza técnicas de aislamiento de contenedores para limitar el acceso del Firecracker al kernel del host mientras se ejecuta VMM y otros componentes de administración.
de Firecracker como subprocesos de un único proceso en el espacio de usuario del host. Dentro del proceso Firecracker, la carga de trabajo del usuario se ejecuta en otros subprocesos. Los subprocesos de carga de trabajo ejecutan el sistema operativo invitado de la máquina virtual y cualquier programa que se ejecute en el invitado. La ejecución del código del usuario en la máquina virtual invitada restringe su interacción directa con el host a interacciones preestablecidas con KVM y ciertas partes de los subprocesos de administración de Firecracker. Entonces, desde la perspectiva del kernel del host, el VMM y la VM, incluido el código del usuario, se ejecutan en el mismo proceso. Esta es la razón por la que AWS afirma que cada VM reside en un único proceso. Pero, dado que la VM está aislada mediante técnicas de virtualización de hardware, el código del usuario, el kernel invitado y el VMM operan en espacios de direcciones separados. Por lo tanto, el código del invitado no puede acceder de manera arquitectónica o transitoria a VMM o a las direcciones de memoria del kernel del invitado, ya que no están asignadas en el espacio de direcciones del invitado. La superficie de ataque de microarquitectura restante se limita a ataques MDS que filtran información de los buffers internos de la CPU ignorando los límites del espacio de direcciones y ataques Spectre donde un atacante manipula la predicción de rama de otros procesos para autofiltrar información.
No se muestra en la Figura 2, pero es igualmente importante para el modelo de amenazas de AWS el aislamiento de funciones entre sí cuando se comparte hardware, especialmente a la luz del requisito de asignación flexible . Además del hecho de que comprometer el kernel del host podría comprometer la seguridad de cualquier invitado, los ataques de microarquitectura dirigidos al hardware del host también pueden amenazar directamente el código del usuario. Dado que un único proceso Firecracker contiene todos los subprocesos necesarios para ejecutar una máquina virtual con una función de usuario, la asignación suave puede ser realizada simplemente por el sistema operativo host [1]. Esto significa que se implementan sistemas de aislamiento de procesos de Linux estándar además del aislamiento de máquinas virtuales.
2.6.1 Recomendaciones de seguridad de petardos. La documentación de Firecracker también recomienda las siguientes precauciones para proteger contra canales laterales de microarquitectura [8]:
• Desactivar SMT
• Habilitar el aislamiento de la tabla de páginas del kernel
• Deshabilitar la fusión de páginas kame y kernel
• Utilice un kernel compilado con mitigación Spectre-BTB (por ejemplo, IBRS e IBPB en x86)
• Verificar la mitigación de Spectre-PHT
• Habilitar la mitigación L1TF. • Habilitar la mitigación Spectre-STL.
• Usar memoria con mitigación Rowhammer
• Desactive el intercambio o utilice el intercambio seguro
Este documento está disponible en arxiv bajo licencia CC BY-NC-ND 4.0 DEED.
[1] El anillo virtualizado 0 y el anillo 3 son una de las razones principales por las que se logra la ejecución de código casi nativo.