x

Microsoft Dynamics 365 Business Central

Descubre el ERP de Microsoft en la nube

art sistemas

La vulnerabilidad del procesador Intel L1TF 

By S. Rojas, Post

Los Microprocesadores modernos son elementos complejos, llenos de optimizaciones diseñadas para exprimir hasta el último gramo de rendimiento disponible.

A continuación, exploraremos en detalle la estructura de un procesador y cómo aparecen las vulnerabilidades que recientemente han hecho que la industria de los microprocesadores deba replantearse la arquitectura de la CPU.

Memorias caché en los procesadores modernos

Las primeras computadoras programables eran máquinas relativamente simples. Ejecutaban programas siguiendo cada una de las instrucciones en orden secuencial y entregando un flujo de programa y resultados predecibles. Esto era adecuado al principio debido mayormente a que las diferentes partes de una computadora funcionaban a la misma velocidad. El procesador (CPU) no era significativamente más rápido que los chips de memoria, por ejemplo. Como resultado, el procesador simplemente esperaba para cargar datos de la memoria a medida que lo necesitaban los programas.

Con el tiempo, el rendimiento relativo de los diferentes componentes de las computadoras cambió drásticamente. Los microprocesadores continuaron aumentando en términos de frecuencia, desde unos pocos miles de instrucciones por segundo a millones y, actualmente, miles de millones de instrucciones por segundo. Al mismo tiempo, el número de transistores que podían caber en un solo chip también aumentaba en muchos órdenes de magnitud, nuevamente de miles a los miles de millones de transistores encontrados en los procesadores de alta gama actualmente. Esto permitió (y requirió) diseños cada vez más complejos. Sin embargo, aunque otros componentes de las computadoras también avanzaron, el rendimiento del procesador avanzó mucho más rápido. Como consecuencia directa de esta disparidad en el rendimiento, los procesadores comenzaron a encontrar “cuellos de botella” en una memoria externa mucho más lenta, a menudo teniendo que esperar cientos, miles o millones de "ciclos" para obtener los datos.

Para mitigar este problema se crearon las MEMORIAS CACHÉS. Las memorias caché almacenan copias de datos usados ​​recientemente mucho más “cerca” (tanto a nivel físico como a nivel lógico) de las unidades funcionales del procesador. Los valores de los datos se almacenan en una memoria externa más lenta y, a medida que el programa los carga, también se almacenan en las memorias caché del procesador. De hecho, los procesadores modernos prácticamente nunca operan directamente en los datos almacenados en la memoria externa. Están optimizados para trabajar en los datos contenidos en sus memorias caché. Estas cachés forman una jerarquía en la cual el nivel más alto (L1) está más cerca del núcleo del procesador, mientras que los niveles más bajos (L2, L3, etc.) están más alejados. Cada nivel tiene diferentes características.

En informática normalmente se dice que el almacenamiento puede ser "pequeño y rápido" o "grande y lento", pero tener ambas cosas es extremadamente caro. Por lo tanto, el caché L1 de un procesador moderno es “solo” del orden de 16 o 32 Kilobytes mientras que el L3 (a veces llamado LLC – Caché de último nivel) más cercano a la memoria externa puede ser de 32 Megabytes o incluso más grande en los servidores modernos. La forma en que las memorias caché están organizadas varía de un diseño a otro, pero en un diseño clásico, las copias de los mismos datos estarán contenidas dentro de múltiples niveles de la memoria caché, dependiendo de cuándo fue utilizada por última vez. Un algoritmo de sustitución dentro del procesador desalojará las entradas más antiguas de la memoria caché de datos L1 para cargar nuevos datos mientras que se mantienen copias dentro de la L2 o L3. Por lo tanto, es raro que los datos utilizados recientemente se carguen desde la memoria, pero puede que provengan de uno de los cachés más grandes, más lentos y de menor nivel que la L1 cuando sea necesario.

Los chips de procesador modernos contienen múltiples núcleos. Cada uno de éstos se comporta como una computadora tradicional de un solo procesador. Cada uno puede ejecutar programas individuales o ejecutar múltiples hilos de programa, así como trabajar con la memoria compartida. Cada núcleo tiene su propio caché L1 y también puede tener su propio caché L2, mientras que el L3, más grande, suele ser compartido por todos los núcleos de un procesador.

A continuación, el procesador emplea una técnica conocida como "coherencia" de la memoria caché para mantener sincronizada la copia interna de cada núcleo de una ubicación de memoria con cualquier copia almacenada en la memoria caché de otro núcleo. Esto se logra llevando un seguimiento del propietario de la memoria y se enviando mensajes a los demás cada vez que se actualiza la memoria.

La naturaleza compartida de los cachés aporta un gran beneficio, pero también son una fuente potencial de vulnerabilidades. A principio de 2018 se publicaron las vulnerabilidades "Spectre" y "Meltdown" en la que los cachés pueden servir como un potencial “side-channel” a través del cual se puede filtrar la información. Mediante “side-channel”, no se accede directamente a los datos, sino que se deducen debido a alguna propiedad del sistema afectado.

Como decíamos antes, la diferencia en el rendimiento entre la memoria caché y la memoria principal externa es lo que motiva el uso de esta caché. Desafortunadamente, esta misma diferencia se puede aprovechar, midiendo la diferencia relativa en los tiempos de acceso para determinar si una ubicación de memoria está en la memoria caché o no. Si está contenido dentro de la memoria caché, se ha accedido recientemente como resultado de alguna otra actividad del procesador.

foto3

Direccionamiento de memoria virtual y física

En los principios de la informática los programadores tenían que preocuparse de la memoria física a la hora de desarrollar, ya que era necesario realizar un seguimiento explícito del contenido de cada ubicación de memoria física, con el fin de evitar posibles conflictos con otras aplicaciones.

En cambio, las memorias cachés de los procesadores modernos generalmente usan una combinación de direccionamiento físico y virtual para referenciar los datos, donde las direcciones físicas se utilizan para acceder a la memoria principal. Conceptualmente, se puede pensar en la memoria RAM del ordenador como una matriz gigante de valores que comienza en la dirección cero y continúa hasta que se agote la memoria. La cantidad de memoria física varía, desde los 4 GB en una computadora portátil básica a varios Terabytes si hablamos de servidores de gama alta.

Los sistemas actuales usan direccionamiento de memoria virtual. Este direccionamiento virtual significa que el sistema operativo puede presentar cada aplicación con su propia vista de la memoria conceptualmente aislada. Los programas ven la memoria como un rango casi infinito dentro del cual pueden hacer lo que quieran. Cada vez que el programa accede a una ubicación de memoria, la dirección se traduce utilizando un hardware dentro del procesador conocido como Unidad de Administración de Memoria (MMU). MMU trabaja mano a mano con el Sistema Operativo (OS), que crea y administra un conjunto de tablas de páginas que convierten las direcciones de memoria virtual en física. La memoria física se divide en pequeños trozos, conocidos como páginas, que generalmente tienen un tamaño de 4 KB. Las denominadas “tablas de páginas” son las encargadas de almacenar las traducciones para estas páginas, de modo que un rango de tamaño de 4KB de direcciones virtuales se traducirá en un rango de 4KB físicas. Como optimización adicional, las tablas de páginas tienen naturaleza jerárquica, de modo que una única dirección se decodifica a través de una secuencia de "pasos" pasando por varias “capas” de tablas hasta que se ha traducido por completo.

Las MMU contienen hardware específico capaz de leer e incluso actualizar las tablas de página administradas por el SO. Estos incluyen los denominados "walkers" de tablas de páginas que realizan el proceso de ir recorriendo las tablas, así como hardware adicional capaz de actualizar las entradas de la tabla de páginas para indicar los datos a los que se ha accedido recientemente. El sistema operativo utiliza estos “walkers” para rastrear qué datos se pueden "paginar" temporalmente o "intercambiar" al disco duro si no se han utilizado recientemente. Una entrada de la tabla de páginas puede marcarse como "no presente", lo que significa que cualquier intento de acceder a la dirección asociada generará una condición especial conocida como "Terminal Fault" que indica al sistema operativo que debe realizar acciones adicionales. Por lo tanto, el sistema operativo intercepta los intentos de acceder a los datos que se han intercambiado con anterioridad al disco, vuelve a colocarlos en la memoria y reanuda una aplicación sin que ésta se entere de lo que ha sucedido, más allá de un retardo adicional en el acceso los datos. Como resultado, la paginación se usa para crear la ilusión de tener más memoria física de la que realmente hay disponible.

Como podemos imaginar, los recorridos por tablas de páginas resultan poco eficientes. Las tablas de página gestionadas por el sistema operativo se alojan en una memoria física real que debe cargarse en el procesador para leerse. Recorrer una tabla de páginas puede llevar bastantes accesos de memoria y sería demasiado lento si se hiciera constantemente. Por lo tanto, el procesador guardará en caché el resultado de estos recorridos de tabla en una estructura de procesador separada, conocida como “Translation Lookaside Buffer” (o TLB)”. Las direcciones traducidas recientemente se resuelven mucho más rápido en direcciones físicas porque el procesador solo necesita buscar el TLB. Si no existe una entrada, entonces el procesador realiza el recorrido por las páginas (operación mucho más lenta) y cargará la entrada del TLB, posiblemente desalojando otra entrada en el proceso. Hace unos meses se describió una vulnerabilidad relacionada con TLB, conocida como vulnerabilidad TLBleed.

Cuando los programas leen o escriben en la memoria, los accesos pasan por la caché de datos de mayor nivel (L1), que es (en la mayoría de las implementaciones modernas) conocida como VIPT, que se traduciría al español como “Indizado virtual con marcado físico”. Esto significa que la memoria caché utiliza direcciones parte virtuales y parte físicas para buscar una ubicación de memoria. A medida que va examinando direcciones virtuales que leer, el procesador va realizando una búsqueda simultánea del TLB para encontrar la traducción de página virtual a física, buscando en la caché una posible coincidencia de entrada mediante el desplazamiento en una sola página.

En resumen, el proceso de lectura desde una ubicación de memoria virtual es bastante complejo en casi todos los procesadores modernos. Como curiosidad, esta optimización de diseño explica por qué los cachés de datos L1 son típicamente de 32 KB en procesadores cuyo tamaño de página es de 4 KB, ya que están limitados por el número de bits de desplazamiento en una sola página a la hora de comenzar la búsqueda en caché.

Los procesadores Intel contienen una optimización adicional en la forma en la que manejan el recorrido por las tablas de páginas y "Terminal Fault" (accesos a páginas no presentes).  Hablaremos más de esto tras analizar la naturaleza especulativa de los procesadores. Esta última parte está muy relacionada con las vulnerabilidades publicadas a principios de 2018, conocidas como “Spectre” y “Meltdown”.

foto1

Ejecución “Out of Order” y especulativa

La utilización de cachés ha permitido mejorar mucho el rendimiento de los microprocesadores en comparación con otras partes de una plataforma informática. Esto ha llevado a la creación de ingeniosas e importantes innovaciones, como la ejecución “Out of Order” (OoO) y especulativa, que han contribuido en gran medida a los constantes aumentos en la eficiencia y rendimiento observados durante últimas décadas en los procesadores. A medida que el número de transistores aumenta y los procesadores se vuelven más complejos, las optimizaciones van avanzando, pero todas siguen siendo construidas sobre la base de diseño de “OoO”.

Con OoO, el procesador se divide a nivel lógico en un “front-end” (denominado “in order”) y un “back-end” (denominado “Out of Order”). El front-end tiene como entrada el programa del usuario. Este programa es de naturaleza secuencial, formado a partir de bloques de código y ramificaciones ocasionales (como operaciones condicionales "if") a otros bloques en función de la evaluación condicional de valores de datos. El front-end envía las instrucciones contenidas dentro del programa al back-end (Out of Order).

A medida que se envía cada una de estas instrucciones, se asigna una entrada dentro de una estructura de procesador conocida como Búfer de Reordenación (ROB). El ROB permite el seguimiento de procedencia de los datos, lo que permite la aplicación OoO, en el que las instrucciones se pueden ejecutar en el orden más eficiente, en vez de en el de llegada, siempre que no suponga una diferencia en el resultado final. En definitiva, tendremos la misma respuesta de un modelo de ejecución secuencial, pero disfrutando de una secuencia de ejecución internamente optimizada.

En la práctica, el ROB sirve para convertir una máquina “en orden” en lo que se conoce como una máquina de "flujo de datos", en la que las instrucciones dependientes se esperan para ejecutarse hasta que sus valores de entrada estén disponibles. Siempre que una entrada en el ROB que contiene una instrucción de programa tenga todos sus valores dependientes disponibles, se pasará a las unidades funcionales del procesador y el resultado se guardará en el ROB para continuar ejecutando las instrucciones. A medida que las entradas en el ROB se van haciendo más antiguas (cuya ejecución se solicitó hace más tiempo), se las reconoce como “retiradas” y estarán disponibles para la vista del programador. Esto se conoce como el estado "arquitectónicamente visible" y es idéntico al que se obtiene a partir de una ejecución secuencial. De esta manera se consiguen mejoras significativas en el rendimiento.

Consideremos este ejemplo de pseudocódigo:

  • 1 CARGA R1
  • 2 CARGA R2
  • 3 R3 = R1 + R2
  • 4 R4 = 2
  • 5 R5 = R4 + 1
  • 6 R6 = R3 + 1

En el ejemplo se usa la letra R para designar pequeñas porciones de memoria interna del procesador conocidas como “registros”, o más ampliamente como GPR (General Purpouse Registers). Normalmente hay solo unos pocos registros de este tipo, 16 en el caso de las máquinas Intel x86-64. Por comodidad aquí, los numeramos R1, R2, etc., aunque en realidad tienen otros nombres, como RAX, RBX, etc.

En un modelo de ejecución secuencial clásico, las dos primeras instrucciones provocarán que la máquina tenga que esperar ("stall") mientras se accede a las ubicaciones lentas de la memoria externa. Las cachés aceleran este proceso, pero incluso si los dos valores están contenidos dentro de algún nivel de la memoria caché del procesador, puede haber un retraso mientras se completan las instrucciones de carga. En lugar de esperar, una máquina OoO se saltará, notando que mientras que la instrucción número 3 depende de las dos primeras (tiene una "dependencia de datos"), las instrucciones 4 y 5 son independientes. De hecho, esas dos instrucciones no tienen ninguna dependencia sobre las instrucciones anteriores. Se pueden ejecutar en cualquier momento y ni siquiera dependen de la memoria externa. Sus resultados se almacenarán en el ROB hasta el momento en que las instrucciones anteriores se hayan completado, momento en el que también se retirarán.

La instrucción número 6 en el ejemplo anterior se conoce como dependencia de datos. Al igual que la instrucción 3 depende de los resultados de cargar las ubicaciones de memoria A y B, la instrucción 6 depende del resultado de sumar esas dos ubicaciones de memoria. Un programa real tendrá muchas de esas dependencias, todas rastreadas en el ROB, que podría ser de un tamaño bastante grande. Como resultado del tamaño de esta estructura, es posible aplicar otra mejora sobre OoO.

La ejecución especulativa se basa en la ejecución de OoO. En ella, el procesador nuevamente realiza las instrucciones en una secuencia diferente de aquella en la que se escribe el programa, pero también especula más allá de las ramas en el código del programa.

Consideremos un programa tal como:

  • SI (está_lloviendo)
    • Coger_paraguas ()

El valor "está_lloviendo" puede estar contenido en una memoria externa (más lenta). Por tanto, sería útil para el procesador poder continuar realizando un trabajo útil mientras se espera que la condición se "resuelva". En lugar de estancarse (como en un diseño clásico más simple), un procesador especulativo predecirá (especulará) la dirección de una rama en función de la historia. El procesador continuará ejecutando instrucciones siguiendo esa rama, pero etiquetará estos resultados en el ROB para indicar que son especulativos y es posible que haya que desecharlos. Un sistema de puntos de control dentro del procesador permite que los datos especulados se desechen rápidamente sin que el programador lo detecte, pero algunos artefactos de actividad especulativa pueden seguir siendo visibles.

“Gracias” a de las vulnerabilidades de Spectre sabemos que estos predictores de ejecución del procesador pueden ser engañados (entrenados) para predecir de siguiendo ciertos patrones. Entonces, el código se puede ejecutar de forma especulativa y tiene un efecto observable en los cachés del procesador. Si podemos encontrar "artefactos" adecuados en el código existente, podemos causar la ejecución especulativa deliberada de código que, en condiciones normales, no debería formar parte del flujo de un programa. En el caso concreto de Spectre-v1 consiste en exceder los límites de una matriz y luego hacer que se ejecuten instrucciones dependientes que alterarán las ubicaciones en el caché, de las cuales podemos inferir el valor de unos datos a los que no deberíamos tener acceso).

Más sobre las vulnerabilidades de Spectre aquí.

 

Aumentando la especulación en los procesadores Intel

Los procesadores actuales llevan la especulación mucho más allá que simplemente el hecho adelantarse en un programa. Dado que el sistema especulativo ya está en uso, los proveedores de microprocesadores como Intel, persiguiendo una ventaja competitiva, han ido más allá con la ejecución especulativa, actuando sobre todo tipo de posibles estados adicionales dentro del procesador. Esto incluye especulación sobre el resultado de un recorrido de tabla de páginas por el “Walker” de páginas MMU durante la traducción de una dirección de memoria virtual a física.

Intel define el término "Terminal Fail" como la condición que surge cada vez que una entrada de tabla de páginas (PTE) se encuentra “no presente " durante un recorrido de tabla de páginas. Esto normalmente ocurre porque el sistema operativo ha intercambiado una página en el disco (o aún no ha solicitado que se cargue) y ha marcado la página como “no presente” para activar este “error” después en el acceso. Como vimos anteriormente, esto permite que un sistema operativo proporcione mucha más memoria virtual que la memoria física dentro de la máquina.

El sistema operativo hace esto al usar los bits PTE “no presente" para almacenar varios datos del servicio de limpieza, como la ubicación física en el disco que contiene el contenido de la página. El Manual del Desarrollador de Software de Intel (SDM) establece que, para las páginas marcadas como “no presentes” de este modo, todos los demás bits (salvo el presente) serán ignorados de manera que estén disponibles para el sistema operativo. Tanto Linux como Windows, así como otros sistemas operativos, hacen un uso intensivo de estos bits PTE para páginas "no presentes" con el fin de admitir el intercambio y otros fines permitidos.

Como mencionamos anteriormente, los procesadores como Intel usan una optimización en la que la traducción de direcciones virtuales se realiza en paralelo con el acceso de caché a la memoria caché de datos L1 (Indizado virtual con marcado físico, VIPT). Como optimización adicional, Intel se dio cuenta de que en el caso óptimo (una ruta lógica crítica), un valor de datos cargado desde la memoria estaría presente en la memoria caché y habría una traducción de tabla de páginas válida disponible. Dicho de otra manera, sería menos probable que la tabla de páginas tuviese una entrada marcada como "no presente".

Como resultado, los procesadores modernos de Intel retrasan la verificación de bit de presencia y reenvían el contenido de las entradas de tabla de página (PTE) directamente a la lógica de control de caché mientras realizan simultáneamente todas las otras comprobaciones, incluso si la entrada es válida. Esto nos lleva a un nuevo conjunto de vulnerabilidades relacionadas con la especulación.

Como en pasaba con Meltdown, el ROB se etiqueta para indicar si se debe generar un error "no presente" pero, mientras tanto, el procesador continuará especulando más allá en el programa hasta que este Terminal Fail entre en vigor. Durante esta pequeña ventana, cualquier valor de datos presente en la memoria caché de datos L1 para una página "no presente" se reenviará a instrucciones dependientes. Un ataque similar en concepto a Meltdown puede usarse para leer datos de direcciones físicas si conseguimos crear (o provocar que se cree) una entrada de tabla de página "no presente" para la dirección si, además de lo anterior, esa dirección física está actualmente presente en la caché L1. Esto se conoce como un ataque de falla de terminal L1 o L1TF.

foto2

Más allá del “Bare Metal”

El ataque L1TF contra máquinas físicas utilizadas como host de virtualización (conocidas por el término “Bare Metal”) no es difícil de mitigar mediante de unas cuantas líneas de código del kernel. Este arreglo no tiene un impacto apreciable en el rendimiento, aunque requiere que los sistemas se actualicen de inmediato. Si esta fuera la solución completa la vulnerabilidad L1TF, no hubiese levantado tanto revuelo, y seguramente, no existirían estas líneas.

Lamentablemente, hay otras variantes de esta vulnerabilidad.  Una de ellas se refiere a las “Software Guard Extensions” o SGX. SGX es una tecnología de Intel, también conocida como un "enclave seguro" en el que los usuarios pueden cargar un código de software que se ejecutará en un sitio protegido especial evitando que ese software sea observado incluso por el propio sistema operativo. Un caso típico en el que se utiliza SGX es para proporcionar protección contra manipulaciones de gestión de derechos y encriptación. Intel ha ido securizado SGX con sucesivas actualizaciones del microcódigo de procesador, diseñadas para evitar que estas extensiones se vean comprometidas a través de la variante específica SGX "Foreshadow" de la vulnerabilidad L1TF.

Otra variante de L1TF afecta a los entornos virtualizados. En infraestructuras virtuales, los procesadores Intel implementan una tecnología conocida como EPT (Extended Page Tables) en la cual las tablas de páginas son administradas de forma conjunta por el “hipervisor”, el sistema operativo invitado y el hardware. EPT reemplaza al sistema anterior gestionado únicamente por software, en el que el hipervisor estaba obligado a utilizar tablas de páginas ocultas. En ese diseño, cada vez que un sistema operativo invitado deseaba actualizar sus propias tablas de páginas, el hipervisor debía interceptar (pausar al invitado), actualizar sus propias tablas ocultas (tal como las utiliza el hardware real) y reanudar al invitado. Esto era necesario para garantizar que un invitado nunca crease tablas de páginas que accedan a la memoria no autorizada por el hipervisor.

EPT mejora significativamente el rendimiento porque un sistema operativo invitado puede administrar sus propias tablas de páginas como lo haría si estuviese corriendo directamente sobre el hardware. En EPT, cada acceso a la memoria se traduce varias veces, primero utilizando las tablas de la página invitada (de una dirección virtual invitada a una dirección física invitada) y luego por las tablas de la página del hipervisor (desde una dirección física invitada a una dirección física del host). Este proceso aporta todos los beneficios de la traducción de páginas asistidas por hardware nativo mientras permite que el hipervisor mantenga el control, pudiendo administrar qué memoria física está disponible.

Cuando Intel implementó EPT, se trataba de una extensión de la infraestructura de paginación existente. Probablemente tan solo se añadiera la nueva fase de traducción sobre la capa de tradicional. Esto funcionaba bien, con una pequeña excepción: una condición de “Terminal Fail” que surja en la traducción de direcciones virtuales a físicas invitadas, podría dar como resultado que una dirección física invitada no traducida sea tratada como una dirección física del host y, por lo tanto, reenviada a la memoria caché de datos L1. Como resultado, es posible que un invitado malintencionado cree una entrada de tabla de página EPT que esté marcada como "no presente" y que también contenga una dirección física de host de la que le gustaría leer. Si esa dirección física del host está en la caché de datos L1, podría leerla.

Este método de L1TF es una amenaza importante para entornos virtualizados, especialmente aquellos que contienen máquinas no confiables (sobre las que no se tiene el control). Afortunadamente, no es extremadamente difícil de solventar. Para que un exploit sea exitoso, se requiere que los datos estén contenidos dentro de la caché de datos L1 en una máquina vulnerable, por lo que en aquellos casos donde pudiera haber secretos u otros datos de posible interés para un software malintencionado, es posible provocar que L1 se vacíe antes de regresar a una máquina virtual invitada. Realizar esta descarga conlleva algo de trabajo, pero una recarga desde la caché L2 a la L1 es rápida (solo unos pocos cientos de ciclos) y utiliza un bus interno con gran ancho de banda existente en estos procesadores. Por lo tanto, la sobrecarga supone tan solo un pequeño porcentaje del rendimiento.

Por desgracia, de nuevo, existe una complejidad adicional a la vulnerabilidad L1TF con respecto a Intel Hyper-Threading.

 

Multi-Threading simultáneo

Los procesadores de Intel suelen implementar una tecnología conocida como Multi-Threading Simultáneo (SMT). SMT fue inventado por Susan Eggers, quien a principios de 2018 recibió el prestigioso Premio Eckert-Mauchly por sus contribuciones al campo.

Eggers se dio cuenta, durante la los 90, de que se podía lograr un mayor rendimiento a nivel de subprocesos de programa dividiendo un único núcleo de procesador físico en varios hilos. En lugar de duplicar todos los recursos de un núcleo completo, SMT duplica solamente los recursos esenciales para tener dos hilos separados que se ejecutan al mismo tiempo. La idea es que los recursos más caros, como las memorias caché, se puedan compartir entre dos subprocesos del mismo programa porque a menudo operan con los mismos datos. En lugar de competir por los recursos, estos hilos sirven para extraer datos útiles de esta caché compartida, por ejemplo, en el caso de una situación "cliente-servidor" en la que un hilo genera datos utilizados por otro hilo.

Intel fue uno de los primeros en adoptar comercialmente SMT. Su implementación, conocida como "Hyper-Threading", ha sido muy efectiva, tanto que los usuarios a menudo se refieren a la cantidad de núcleos e hilos en sus máquinas como una característica esencial. En Hyper-Threading, existen dos subprocesos “hermanos” dentro de un solo núcleo. Cada uno de ellos está programado dinámicamente para usar los recursos disponibles del núcleo, de tal forma que se pueda obtener una mejora general en el rendimiento (de hasta un 30% aplicaciones diseñadas para que operen con datos compartidos). A su vez, se diseñan técnicas para reducir el impacto del efecto negativo que ocurre cuando dos hilos no comparten estos recursos (se ejecutan en núcleos físicos separados).

No obstante, hay muchas ocasiones (como en las aplicaciones de HPC) donde el potencial de interferencia entre hilos en distintos núcleos supera el posible beneficio. En estos casos, se hace preferible desactivar Hyper-Threading, desde la BIOS del equipo.

Deshabilitar Hyper-Threading también es común en el ámbito de la virtualización. Suele ser preferible asignar núcleos completos (lo que se conoce como "programación básica") a las instancias de máquinas virtuales. Dos máquinas virtuales diferentes que comparten un único núcleo pueden interferir con el rendimiento de los demás, ya que dividen las memorias cachés subyacentes junto con otros recursos. Sin embargo, a pesar de los problemas que pueden surgir, siempre es tentador tratar los hilos como núcleos extra disponibles. Por lo tanto, no es extraño en las implementaciones actuales dividir máquinas virtuales a través de Hyper-Threading entre procesos del mismo núcleo, y tecnologías como OpenStack lo hacen de forma predeterminada. Aunque esto no es por sí una buena idea, el impacto en términos de seguridad es mucho más importante ahora tras el conocimiento de la vulnerabilidad L1TF.

Hyper-Threading consiste en que los hilos se ejecuten simultáneamente y, como resultado, es posible que un hilo esté ejecutando código de hipervisor o una instancia de máquina virtual, mientras que, al mismo tiempo, el hilo “hermano” esté ejecutando invitado malintencionado. Al ocurrir esto, aunque el caché de datos L1 se vacíe, desafortunadamente no es posible evitar que el invitado malintencionado observe las cargas de caché realizadas por su hilo “hermano”. Por lo tanto, si dos máquinas virtuales diferentes se ejecutan en el mismo núcleo al mismo tiempo, es imposible garantizar que no puedan realizar un ataque L1TF el uno contra el otro y robar información no autorizada.

El impacto concreto de L1TF en Hyper-Threading depende del caso de uso específico y del entorno de virtualización que se utilice. En algunos casos, es posible que los proveedores de servicios en la nube (que a menudo utilizan hardware específico para garantizar el aislamiento) tomen medidas para evitar este problema.

En otros casos, como en un entorno empresarial tradicional con máquinas virtuales invitadas que no son de confianza, puede ser necesario desactivar Intel Hyper-Threading. Al revés

Conclusión

La vulnerabilidad del procesador Intel L1TF (Termina Fail) es compleja y, en algunos casos, requiere acciones específicas de los usuarios y administradores de sistemas para lograr una completa solución. En VS Sistemas hemos estado trabajando para prepararse para la divulgación pública coordinada, preparar métodos de acción, documentación, capacitación y demás materiales necesarios para ayudar a mantener seguros a nuestros datos y los de nuestros clientes. Recomendamos siempre seguir las buens prácticas de seguridad, especialmente la implementación de las actualizaciones de seguridad recomendas por los fabricantes.

 

Referencias:

https://www.intel.com/content/www/us/en/architecture-and-technology/l1tf.html

https://www.redhat.com/en/blog/understanding-l1-terminal-fault-aka-foreshadow-what-you-need-know

https://blogs.technet.microsoft.com/srd/2018/08/14/analysis-and-mitigation-of-l1-terminal-fault-l1tf/

 

Comparte este artículo

Este sitio web utiliza Cookies propias y de terceros. Si continúas la navegación, estás aceptando el uso de las cookies. Entendido | Más información