2 puntos por GN⁺ 2023-11-15 | 1 comentarios | Compartir por WhatsApp
  • En algunos procesadores Intel, si rep movs y un prefijo rex.r duplicado coinciden con la optimización FSRM, la CPU puede entrar en un estado de “glitch” fuera de las reglas normales
  • La causa está en la decodificación laxa de prefijos de x86 y en que un prefijo rex que en principio no debería tener efecto en instrucciones con operandos implícitos como movsb termina tomando una ruta de optimización específica
  • El pipeline de verificación de Google descubrió en agosto de 2023 resultados impredecibles con esta combinación, y observó saltos inesperados, ignorar saltos incondicionales y anomalías en el registro del puntero de instrucción de xsave y call
  • Si varios núcleos activan el mismo bug al mismo tiempo, pueden producirse machine check exception y bloqueo del sistema; además, se reproduce incluso dentro de una VM invitada sin privilegios, lo que lo vuelve importante para entornos en la nube
  • Intel publicó una actualización de microcódigo para los procesadores afectados; si no es posible actualizar, se pueden desactivar las fast strings desde IA32_MISC_ENABLE, aunque con una gran pérdida de rendimiento

Prefijos x86 y rep movsb

  • rep movsb es una forma idiomática en x86 de mover memoria: si se configuran origen, destino, dirección y contador, el procesador se encarga de la copia repetida
  • La instrucción real es movsb, y rep es un prefijo (prefix) que modifica esa instrucción para que se repita varias veces
  • La decodificación de instrucciones x86 es relativamente laxa, así que los prefijos sin efecto o en conflicto normalmente se ignoran
    • Los compiladores pueden usar estos prefijos duplicados para rellenar una sola instrucción hasta el límite de alineación deseado
  • rex, vex y evex son prefijos que cambian la forma en que se decodifica la instrucción siguiente

La combinación problemática del prefijo rex

  • i386 tenía 8 registros de propósito general, así que bastaban 3 bits para especificar un registro, pero x86-64 tiene 16 y necesita bits adicionales
  • El prefijo rex proporciona bits extra que la siguiente instrucción puede usar al codificar operandos
    • Suele escribirse como rex.rxb, donde los bits b, x, r y w pueden activarse opcionalmente
  • movsb no declara operandos en la instrucción, sino que todos son implícitos, por lo que los bits rex de rex.rxb rep movsb no deberían significar nada
  • Normalmente el procesador ignora en silencio este tipo de prefijos rex, pero en sistemas que soportan fast short repeat move esta combinación deriva en la vulnerabilidad

FSRM y procesadores afectados

  • FSRM es una función introducida en Ice Lake para compensar las limitaciones de ERMS al manejar cadenas cortas
  • ERMS (enhanced repeat move/store) permite que el microcódigo gestione la alineación de búferes y escrituras amplias para acelerar el código tradicional con rep movsb
    • Su costo inicial de configuración es alto, por lo que no encaja bien con cadenas muy cortas
  • FSRM está diseñado para procesar más rápido movimientos cortos de 128 bytes o menos
  • Puede comprobarse el soporte con la bandera fsrm en la línea flags de /proc/cpuinfo
  • Ejemplos de procesadores que incluyen FSRM:
    • Ice Lake
    • Rocket Lake
    • Tiger Lake
    • Raptor Lake
    • Alder Lake
    • Sapphire Rapids
  • Esta lista no es exhaustiva; para la lista completa hay que consultar el advisory de Intel INTEL-SA-00950

Descubrimiento y reproducción

  • El pipeline de verificación de Google ejecuta dos formas de programas generados aleatoriamente con la técnica Oracle Serialization y luego compara si el estado final es el mismo
  • En agosto de 2023, al añadir un prefijo rex.r duplicado a rep movs optimizado con FSRM, aparecieron resultados impredecibles
  • Los comportamientos anómalos observados fueron:
    • saltos hacia ubicaciones inesperadas
    • ignorar saltos incondicionales
    • el puntero de instrucción no se registra correctamente en instrucciones xsave o call
    • el depurador reporta estados imposibles
  • Si varios núcleos activan el mismo bug, el procesador puede reportar machine check exception y detenerse
  • También puede reproducirse dentro de una VM invitada sin privilegios, por lo que es un problema de seguridad importante para proveedores de nube
  • Las herramientas de reproducción y materiales de investigación están publicados en el security research repository de Google
    • También se ofrece un mirror local de la herramienta icebreak en icebreak.tar.gz
  • icebreak intenta reproducir el problema especificando pares de núcleos distintos
    • En sistemas no afectados, no debería haber salida, como si fuera un bucle infinito
    • En sistemas afectados, cada reproducción exitosa imprime un .
    • En núcleos hermanos SMT puede observarse una bifurcación aleatoria
    • En núcleos hermanos SMP del mismo paquete puede observarse un machine check
    • Si no se especifican dos núcleos distintos, puede hacer falta un hammer thread

Posible causa e impacto observado

  • Como el comportamiento del microcódigo en sistemas modernos no es público, la causa raíz solo puede tratarse como una teoría basada en observaciones
  • La CPU se divide a grandes rasgos en frontend y backend
    • El frontend obtiene y decodifica instrucciones para generar μops
    • El backend ejecuta instrucciones fuera de orden, guarda resultados en el ROB (reorder buffer) y luego los retira
  • Este bug parece hacer que el frontend calcule mal el tamaño de la instrucción movsb, provocando un estado en el que entradas posteriores del ROB quedan asociadas con direcciones incorrectas
  • En ese estado aparece una situación de confusión donde el puntero de instrucción se calcula mal
  • El sistema puede recuperarse finalmente a un estado internamente coherente, pero los resultados intermedios pueden ser incorrectos
  • Si varios núcleos SMT o SMP entran a la vez en este estado, puede dañarse suficiente estado microarquitectónico como para forzar un machine check
  • Puede corromper el estado del sistema lo suficiente como para provocar un machine check, y se ha observado interferencia entre hilos durante la ejecución de procesos programados en núcleos hermanos SMT
  • No está confirmado si esa corrupción puede controlarse con la precisión suficiente como para lograr una escalada de privilegios

Cómo responder

  • Intel publicó microcódigo actualizado para todos los procesadores afectados en INTEL-SA-00950
  • Es posible que el sistema operativo o el proveedor del BIOS ya ofrezcan la actualización
  • Si no se puede actualizar, pueden desactivarse las fast strings mediante el registro específico por modelo IA32_MISC_ENABLE
  • Desactivar fast strings provoca una gran caída de rendimiento, así que no debería usarse salvo que sea realmente necesario

Material relacionado sobre bugs de CPU

1 comentarios

 
GN⁺ 2023-11-15
Opiniones de Hacker News
  • Artículo relacionado: https://cloud.google.com/blog/products/identity-security/goo...
    Contenido tomado de https://news.ycombinator.com/item?id=38268043, pero los comentarios se fusionaron aquí

  • Leer este artículo me hizo darme cuenta de lo poco que sé sobre el hardware en el que corre mi software
    Dice que “los prefijos permiten activar o desactivar funciones para cambiar el comportamiento de las instrucciones”, y me pregunto por qué se necesita un “prefijo” para activar y desactivar funciones
    ¿Es para cambiar funciones dinámicamente sin entrar al BIOS?

    • Conviene leer https://wiki.osdev.org/X86-64_Instruction_Encoding#Legacy_Pr...
      El prefijo REP es el más común; sirve para hacer que la misma instrucción se repita una cantidad variable de veces
      La cantidad de repeticiones se toma del registro CX, y permite hacer muy breves bucles comunes, como mover objetos desde memoria
      La función memcpy a menudo se inserta inline como una sola instrucción REP MOVS y, si hace falta, con una instrucción que copia el contador a CX
      El prefijo REX también es bastante común porque los programas de 64 bits manejan con frecuencia valores y direcciones de 64 bits
      Ningún prefijo alterna algo configurable globalmente mediante el BIOS ni nada parecido; todos solo especifican qué debe hacer la siguiente instrucción
    • En este caso, un “prefijo” sirve en general para ampliar el espacio de codificación de instrucciones
      Los modos de direccionamiento poco usados llevan un “prefijo de segmento” que hace usar un segmento distinto de DS, y el prefijo “REX” de x86_64 agregó bits a los campos de registros para poder usar 16 registros de propósito general
      De forma similar, el prefijo “LOCK”, aunque su especificación original era deficiente, hace que algunas operaciones de memoria sean atómicas respecto del resto del sistema, por ejemplo para implementar compare-and-set con “LOCK CMPXCHG”
      Otras arquitecturas de CPU también expresan estas operaciones, pero normalmente las encajan dentro del espacio de instrucciones existente, lo que requiere más bits para representar todas las instrucciones
      El prefijo “REP”, que es el problemático aquí, es casi una excepción: es un prefijo de repetición de microcódigo heredado desde tiempos antiguos
      Aun así, representa operaciones que hoy siguen siendo sensibles al rendimiento, como memset/memmove, por lo que a los fabricantes de CPU les vale la pena seguir optimizándolo, y este bug parece haber surgido en ese proceso
    • Los prefijos son modificadores de una instrucción específica que ejecuta el procesador; se usan, por ejemplo, para controlar el tamaño de los operandos o habilitar bloqueos para concurrencia
    • x86 se diseñó en 1978 básicamente con el propósito de ejecutar tareas como manejar una impresora láser primitiva o algo similar
      El mayor problema es que “aprovechó eficientemente” el espacio de codificación de instrucciones
      Más tarde, cuando aparecieron nuevas instrucciones y, peor aún, registros adicionales, tuvieron que meter como fuera nuevas variantes de instrucciones, y la forma de hacerlo fue agregar prefijos
    • x86, como arquitectura de conjunto de instrucciones, lleva más de 40 años acumulando agregados, y terminó así porque usa instrucciones de longitud variable
      Cada vez que se extendía el conjunto de instrucciones, se excavaba una parte del espacio de opcodes para meter a la fuerza un nuevo prefijo
      Por lo que Intel propuso este año otra forma nueva, parece que esta tendencia seguirá
  • Al ver el proceso de diagnóstico, me acordé de lo que pasó cuando qemu se encontró con repz ret: https://repzret.org/p/repzret/

  • Creo que las reglas de HN deberían prohibir títulos como este
    No te dicen en absoluto de qué trata el enlace, y la URL más bien confunde todavía más
    Si el título no tiene significado, creo que quien publica debería agregar una breve explicación

    • No estoy de acuerdo
      Ya he visto que, cuando el título incluye demasiado contexto, la gente no hace clic en el enlace y en los comentarios solo pule sus propios intereses, como si reaccionara a un tuit
      HN elige un punto medio que fomenta la curiosidad intelectual y hacer clic en el enlace
      Aunque uno se niegue a hacer clic por el título ambiguo, al menos termina respondiendo a quienes sí hicieron clic, y eso me parece mejor que en otros lugares de internet
      Las publicaciones que no ofrecen suficiente recompensa como para justificar un título ambiguo e ingenioso, a diferencia de esta, se caen de la portada
  • El artículo está muy bien escrito
    Sé muy poco de programación en assembly y del conjunto de instrucciones de Intel, y también casi nada de microarquitectura, pero pude seguir la explicación y siento que entendí a grandes rasgos qué está pasando aquí
    Me pregunto si alguien sabe si las CPU de AMD también están afectadas

  • Si el problema realmente es que el procesador se confunde con la longitud de las instrucciones, me parece impresionante que pueda corregirse con microcódigo sin una gran pérdida de rendimiento
    Mi intuición podría estar totalmente equivocada, pero habría pensado que el cálculo de la longitud de instrucciones se sintetiza directamente con compuertas lógicas
    Pensándolo de nuevo, tal vez el decodificador de uOP está bien a nivel de hardware, y una rutina de copia optimizada en microcódigo está intentando inferir algo que no es cierto sobre el flujo de uOP
    Por ejemplo, algo como “ah, esto es rep mov, así que para el bucle basta con retroceder dos uOP”
    No creo que el equipo de CPU de Intel vaya a revelar los detalles

  • No sé bien qué son “ERMS” y “FSRM”, y casi no parece haber buen material en Google.
    Me pregunto si son solo flags de CPUID que indican que rep movsb puede usarse con el máximo rendimiento en lugar de una implementación optimizada de memcpy con SSE, o si son alguna codificación o prefijo especial que hace más rápido a rep movsb.
    Si es lo segundo, no entiendo por qué sería necesario ni cómo se aprovecha FSRM.

    • Encontré este material [1], que también enlaza al manual de optimización de Intel [2].
      Parece que ERMS era una alternativa más barata a AVX, y FSRM era una versión mejor para bloques cortos.
      “Las versiones de bajo costo de procesadores posteriores, como los Kaby Lake Celeron y Pentium lanzados en 2017, no tienen AVX para copias rápidas de memoria, pero sí tienen Enhanced REP MOVSB.
      Además, algunas arquitecturas móviles y de bajo consumo de Intel lanzadas a partir de 2018 no estaban basadas en SkyLake, pero copiaban aproximadamente el doble de bytes por ciclo de CPU con REP MOVSB que las microarquitecturas de generaciones anteriores”.
      “Antes de Fast Short REP MOV(FSRM) de la microarquitectura Ice Lake, Enhanced REP MOVSB(ERMSB) solo era más rápido que una copia con AVX o con registros de propósito general cuando el tamaño del bloque era de al menos 256 bytes.
      En bloques de menos de 64 bytes, el costo interno de arranque de ERMSB era alto, de unos 35 ciclos, por lo que era mucho más lento; la función FSRM tenía como objetivo hacer rápidos también los bloques de menos de 128 bytes”.
      [1] https://stackoverflow.com/a/43837564
      [2] http://www.intel.com/content/dam/www/public/us/en/documents/...
    • FSRM es solo el nombre de una optimización de CPU que afecta al código existente.
      La selección y planificación óptima de instrucciones puede hacerse de forma estática en tiempo de compilación, o de forma dinámica en tiempo de ejecución eligiendo una de varias funciones de biblioteca, o mediante JIT.
      Para detectar en tiempo de ejecución qué planificación de instrucciones es óptima, hay que conocer la CPU real.
      Se podría tener una tabla de todos los modelos de CPU, pero también se le puede preguntar al sistema operativo si la CPU en ejecución implementa esa optimización.
      Linux necesitó un parche para poder informar que la CPU implementaba esa optimización.
      https://www.phoronix.com/news/Intel-5.6-FSRM-Memmove
    • El flag solo indica que en esta CPU rep movsb es rápido, así que no hace falta usar una implementación optimizada con SSE/AVX.
  • Vi que el aviso de Intel [1] dice lo siguiente.
    Intel agradece a los empleados de Intel que descubrieron internamente este problema, y también agradece a los empleados de Google que lo reportaron.
    [1] https://www.intel.com/content/www/us/en/security-center/advi...

    • Me pregunto cuánto antes descubrieron este problema los empleados de Intel que Google.
  • También vale la pena consultar el aviso de Intel con la descripción del impacto: https://www.intel.com/content/www/us/en/security-center/advi...
    “En algunos procesadores Intel(R), una secuencia de instrucciones del procesador puede provocar un comportamiento inesperado, lo que podría permitir que un usuario autenticado, mediante acceso local, habilite una escalada de privilegios, divulgación de información o denegación de servicio”.

    • Aquí, “algunos” parece significar casi todas las CPU Intel x86 fabricadas en los últimos 6 años.
  • Konrad Magnusson, del equipo de Victoria 3 de Paradox Interactive, encontró algo relacionado con esto y mimalloc: https://github.com/microsoft/mimalloc/issues/807
    No sé si está completamente relacionado, pero existe la posibilidad.

    • Si no emitió de alguna manera un prefijo innecesario, parece poco probable que esté relacionado.