Algoritmos criptográficos del kernel de Linux: ¿potencial de explotación?

5

Estoy interesado en muchos de los algoritmos de cifrado en la rama arch / x86 / crypto del núcleo de Linux:

enlace

y esto:

enlace

Muchos de estos algoritmos, como registros como los registros AVX y XMM, no estoy seguro de si el kernel SIEMPRE lo reinicialice desde el contexto anterior (mucho rendimiento si se implementa) . (Por favor, proporcione la referencia a la fuente del kernel). Y si no, si un módulo del kernel está haciendo el cálculo, y luego las claves intermedias pueden ser detectadas desde una aplicación de modo de usuario que mira los registros XMM, ¿No es un riesgo?

Además, si alguien puede modificar estos registros, también puede dañar el cálculo de AES, si se puede interrumpir el cálculo y pasar la programación a una aplicación de modo de usuario.

Para generalizar aún más los dos escenarios anteriores:

Si el proceso A utilizara el registro XMM, ya sea en el modo de usuario o en el modo kernel, durante el proceso de cambio al proceso B, estos valores de registro de XMM se deben respaldar y cambiar. Entonces, si hubiera 10 procesos, todos usando registros XMM, entonces el contexto para cada uno de ellos contendrá la información de XMM. Y dado que esto se debe hacer en el kernel durante el cambio, ¿alguien puede ayudar a señalar la fuente del kernel que muestra que se ha hecho esto?

Aquí se encuentra una pregunta similar a esta (sin respuesta):

enlace

    
pregunta Peter Teoh 16.12.2016 - 01:46
fuente

3 respuestas

2

Echa un vistazo a esta rama: arch / x86 / kernel / fpu (que maneja todas las cosas específicas de la FPU de x86):

Lea este comentario que puede proporcionar la respuesta:

enlace

/*
 * FPU context switching strategies:
 *
 * Against popular belief, we don't do lazy FPU saves, due to the
 * task migration complications it brings on SMP - we only do
 * lazy FPU restores.
 *
 * 'lazy' is the traditional strategy, which is based on setting
 * CR0::TS to 1 during context-switch (instead of doing a full
 * restore of the FPU state), which causes the first FPU instruction
 * after the context switch (whenever it is executed) to fault - at
 * which point we lazily restore the FPU state into FPU registers.
 *
 * Tasks are of course under no obligation to execute FPU instructions,
 * so it can easily happen that another context-switch occurs without
 * a single FPU instruction being executed. If we eventually switch
 * back to the original task (that still owns the FPU) then we have
 * not only saved the restores along the way, but we also have the
 * FPU ready to be used for the original task.
 *
 * 'lazy' is deprecated because it's almost never a performance win
 * and it's much more complicated than 'eager'.
 *
 * 'eager' switching is by default on all CPUs, there we switch the FPU
 * state during every context switch, regardless of whether the task
 * has used FPU instructions in that time slice or not. This is done
 * because modern FPU context saving instructions are able to optimize
 * state saving and restoration in hardware: they can detect both
 * unused and untouched FPU state and optimize accordingly.

Así que para repetir lo que se explica:

a. Modo LAZY: la FPU no se restaura / guarda todo el tiempo, pero solo cuando se usa, y el uso de la FPU también restablecerá un indicador en CR0: TS, por lo que no tenemos que detectar el uso del registro de la FPU todo el tiempo . Pero este modo no es el predeterminado, ya que el ahorro de tiempo / rendimiento mejorado no es significativo, y el algoritmo se vuelve muy complejo, lo que aumenta las sobrecargas de procesamiento.

b. Modo EAGER: Este es el modo por defecto. FPU siempre se guarda y se restaura para cada cambio de contexto. Pero nuevamente, hay una función de hardware que puede detectar si se utiliza la cadena larga de registros de FPU, y cualquiera que se use, solo ese registro se guardará / restaurará, y por lo tanto es muy eficiente en hardware.

Hacer esto no es nada fácil, ya que significó escribir 208 parches en 2015:

enlace

Las instrucciones para guardar todas las FPU: XMM, MMX, SSE, SSE2, etc. se llaman FXSAVE, FNSAVE, FSAVE:

enlace

y la sobrecarga en el kernel de Linux se compara con 87 ciclos.

enlace

Esta forma optimizada de guardar también se puede encontrar en los comentarios a continuación:

 * When executing XSAVEOPT (or other optimized XSAVE instructions), if
 * a processor implementation detects that an FPU state component is still
 * (or is again) in its initialized state, it may clear the corresponding
 * bit in the header.xfeatures field, and can skip the writeout of registers
 * to the corresponding memory layout.
 *
 * This means that when the bit is zero, the state component might still contain
 * some previous - non-initialized register state.

Para detectar que el kernel se activa con el uso de FPU, podemos establecer un punto de interrupción en fpstate_sanitize_xstate en KGDB, y el kernel stacktrace es el siguiente:

Thread 441 hit Breakpoint 1, fpstate_sanitize_xstate (fpu=0xffff8801e7a2ea80) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/fpu/xstate.c:111
111 {
#0  fpstate_sanitize_xstate (fpu=0xffff8801e7a2ea80) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/fpu/xstate.c:111
#1  0xffffffff8103b183 in copy_fpstate_to_sigframe (buf=0xffff8801e7a2ea80, buf_fx=0x7f73ad4fe3c0, size=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/fpu/signal.c:178
#2  0xffffffff8102e207 in get_sigframe (frame_size=440, fpstate=0xffff880034dcbe10, regs=<optimized out>, ka=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:247
#3  0xffffffff8102e703 in __setup_rt_frame (regs=<optimized out>, set=<optimized out>, ksig=<optimized out>, sig=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:413
#4  setup_rt_frame (regs=<optimized out>, ksig=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:627
#5  handle_signal (regs=<optimized out>, ksig=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:671
#6  do_signal (regs=0xffff880034dcbf58) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:714
#7  0xffffffff8100320c in exit_to_usermode_loop (regs=0xffff880034dcbf58, cached_flags=4) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/entry/common.c:248
#8  0xffffffff81003c6e in prepare_exit_to_usermode (regs=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/entry/common.c:283

Utilizando "info thread 441" (ver más arriba) y descubrirá que "Xorg" es el creador del stacktrace anterior, pero de lo contrario, la mayoría de los procesos no usa FPU.

Desde el seguimiento de pila, "get_sigframe ()" es la primera función que parece analizar el uso de FPU:

if (fpu->fpstate_active) {
        unsigned long fx_aligned, math_size;

        sp = fpu__alloc_mathframe(sp, 1, &fx_aligned, &math_size);
        *fpstate = (struct _fpstate_32 __user *) sp;
        if (copy_fpstate_to_sigframe(*fpstate, (void __user *)fx_aligned,
                            math_size) < 0)
                return (void __user *) -1L;
}

Básicamente, lo que está sucediendo aquí es copiar la información de la FPU al puntero de la pila del espacio de usuario (que es "sp").

En resumen, esta parte de la lógica de guardado / copia / restauración de FPU se activa solo con el uso de FPU.

    
respondido por el Peter Teoh 16.12.2016 - 17:47
fuente
4
  

Muchos de estos algoritmos, como los registros AVX y XMM, no estoy seguro de que el kernel SIEMPRE se reinicialice desde el contexto anterior (con un alto rendimiento si se implementa)

Oh, pero lo hace. Al menos, para procesos que utilizan SSE / XMM. El acceso a los registros SSE / XMM se puede desactivar en el hardware (a través de CR4.OSFXSR), lo que también hace que la conmutación de tareas sea más eficiente para los procesos que no utilizan SSE / XMM en absoluto.

  

Y así, si no, si un módulo del kernel está haciendo el cálculo, y luego las claves intermedias pueden ser rastreadas desde una aplicación de modo de usuario mirando los registros XMM, ¿no es un riesgo?

El kernel tiene mucho cuidado de desinfectar todo lo que pudo haber sido tocado por el código del kernel antes de regresar al espacio de usuario.

Es posible que no necesariamente se guarde / restaure, en algunos casos solo se puede borrar con un valor de cero. Pero todo se restaura o se desinfecta.

Donde se hace esto es una pregunta diferente. La suposición predeterminada podría ser que el kernel no toca tales registros avanzados, por lo que la responsabilidad podría estar en los módulos que los usan (al menos para las llamadas al sistema; el cambio de tareas puede ser un poco más complicado que las llamadas al sistema).

    
respondido por el DepressedDaniel 16.12.2016 - 01:57
fuente
2

Bien, pasemos por un código de kernel. Estoy de acuerdo hasta cierto punto con @DepressedDaniel en que el kernel probablemente sea muy cuidadoso al limpiar los registros, el cambio de proceso es un momento crítico en el código del kernel (posiblemente el momento más crítico), por lo que se hace mucha atención para hacer que ese código sea seguro.

Por otro lado, los registros MMX / SSE / SSE2 / 3DNOW / MMXEXT son registros de punto flotante que no se utilizan a menudo. Estoy colocando todas esas extensiones en el mismo barco, ya que son simplemente registros largos junto con un puñado de códigos de operación para operar en ellos. Durante el cambio de proceso, guardar y restaurar un montón de registros de punto flotante que ni siquiera se utilizan es un gran éxito de rendimiento, por lo que el núcleo no se molesta en hacerlo a menos que sea necesario. La pregunta entonces se convierte en "¿Cómo sabe el kernel que necesita lidiar con los registros MMX?" . Eso es algo que debería estar presente en thread.flags , llegaremos a eso en breve.

Después de buscar los registros, encontré que se usan en más lugares de lo que pensé al principio:

  • El código RAID utiliza MM0-MM7, e incluso puede ver dónde utiliza MM0-MM15 en máquinas x86_64 (hay una implementación no SSE también ).
  • código 3DNOW de AMD todavía está en el kernel, aunque duda que se usa en x86_64
  • KVM utiliza el sutff MMX, ya que puede necesitar emularlo
  • Lo suficientemente divertido, no pude encontrar ningún uso de MMX en los módulos de cifrado (aunque no busqué demasiado).

La parte del interruptor de proceso, a primera vista, no parece utilizar los registros MMX. Pero eso, por supuesto, no puede ser cierto ya que sabemos que podemos procesar el proceso RAID o KVM fuera de la CPU y viceversa. La función principal del cambio de proceso es (y casi siempre ha sido) switch_to () . Allí podemos ver:

#define switch_to(prev,next,last) do { \
     if (ia64_psr(task_pt_regs(prev))->mfh && ia64_is_local_fpu_owner(prev)) { \
             ia64_psr(task_pt_regs(prev))->mfh = 0;                  \
             (prev)->thread.flags |= IA64_THREAD_FPH_VALID; \
             __ia64_save_fpu((prev)->thread.fph); \
     } \
     __switch_to(prev, next, last); \
     /* "next" in old context is "current" in new context */ \
     if (unlikely((current->thread.flags & IA64_THREAD_MIGRATION) && \
                  (task_cpu(current) != \
                  task_thread_info(current)->last_cpu))) { \
             platform_migrate(current); \
             task_thread_info(current)->last_cpu = task_cpu(current); \
     } \
} while (0)

Y el pt_regs struct hace no contiene nada sobre MM0-5, pero contiene información sobre MM6-11 (al final).

Entonces, ¿qué da? ¿Dónde diablos se ocupa el núcleo con otros registros? La clave es el procedimiento __switch_to() (bueno, macro, si quieres ser pedante) que está un poco más arriba en el archivo de arriba:

#define __switch_to(prev,next,last) do { \
    if (IA64_HAS_EXTRA_STATE(prev)) \
            ia64_save_extra(prev); \
    if (IA64_HAS_EXTRA_STATE(next)) \
            ia64_load_extra(next); \
    ia64_psr(task_pt_regs(next))->dfh = !ia64_is_local_fpu_owner(next); \
    (last) = ia64_switch_to((next)); \
} while (0)

Y dentro de ia64_save_extra() las marcas de hilo están marcadas y tratado.

También tenga en cuenta que en la anterior ia64_fph_disable() Siempre se llama. El código para los otros procedimientos ia64_fph_* está justo debajo de la macro deshabilitada.

El truco final parece ser el hecho de que en x86_64 los registros MM0-5 se consideran volátiles por la CPU x86_64, por lo tanto son guardados junto con RAX, RCX y RDX . En el formato x86 normal, esto se hace probablemente dentro del código que trata directamente con el material MMX (y perdón por el enlace de wikipedia, no pude encontrar los documentos ABI).

    
respondido por el grochmal 16.12.2016 - 16:15
fuente

Lea otras preguntas en las etiquetas