Algoritmi crittografici del kernel di Linux: potenziale sfruttamento?

5

Sono interessato a molti degli algoritmi crittografici nel ramo arch / x86 / crypto del kernel linux:

link

e questo:

link

Molti di questi algoritmi come registri come i registri AVX e XMM, che non sono sicuro se il kernel SEMPRE lo reinizializza dal contesto precedente (molto performante se implementato) . (Si prega di fornire il riferimento al sorgente del kernel). E così se non, se un modulo del kernel sta facendo il calcolo, e quindi le chiavi intermedie possono essere annusate da un'applicazione usermode guardando i registri XMM, non è un rischio?

Inoltre, se qualcuno può modificare questi registri, può anche corrompere il calcolo AES, se il calcolo può essere interrotto e la programmazione passa a proire un'applicazione usermode.

Per generalizzare ulteriormente lo scenario sopra descritto:

Se il processo A dovesse usare il registro XMM - sia nella modalità usermode o kernel, durante il passaggio del processo al processo B, questi valori dei registri XMM dovrebbero essere backup e attivati. Quindi se ci fossero 10 processi tutti usando i registri XMM, allora il contesto per ognuno di essi conterrà le informazioni XMM.
E dal momento che questi devono essere fatti nel kernel durante il passaggio, qualcuno può aiutare a segnalare la fonte del kernel che mostra che questo è fatto?

Una domanda simile a questa è trovata qui (senza risposta):

link

    
posta Peter Teoh 16.12.2016 - 01:46
fonte

3 risposte

2

Dai un'occhiata a questo ramo: arch / x86 / kernel / fpu (che gestisce tutte le risorse specifiche per FPU di x86):

Leggi questo commento che può fornire la risposta:

link

/*
 * 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.

Quindi, per ripetere ciò che è spiegato:

a. Modalità LAZY: FPU non viene ripristinata / salvata tutto il tempo, ma solo quando viene utilizzata, e l'utilizzo della FPU resetterà anche un flag in CR0: TS, quindi non è necessario rilevare l'utilizzo del registro FPU tutto il tempo . Ma questa modalità non è l'impostazione predefinita, poiché il risparmio di tempo / prestazioni avanzate non è significativo e l'algoritmo diventa molto complesso, aumentando così i costi di elaborazione.

b. Modalità EAGER: questa è la modalità predefinita. FPU è sempre salvato e ripristinato per ogni interruttore di contesto. Ma di nuovo c'è una funzionalità hardware che può rilevare se viene utilizzata la lunga catena di registri FPU - e qualunque sia l'uso, solo quel registro verrà salvato / ripristinato, e quindi è molto efficiente dal punto di vista dell'hardware.

Per fare questo non è un'impresa da poco, perché significava scrivere 208 patch nel 2015:

link

Le istruzioni per salvare tutte le FPU - XMM, MMX, SSE, SSE2 ecc. si chiamano FXSAVE, FNSAVE, FSAVE:

link

e il sovraccarico nel kernel linux è valutato come 87 cicli.

link

Questi metodi ottimizzati per il salvataggio si possono trovare anche nei commenti seguenti:

 * 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.

Per rilevare che il kernel è attivato dall'utilizzo della FPU, possiamo impostare breakpoint su fpstate_sanitize_xstate in KGDB, e lo stacktrace del kernel è come segue:

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

Usando "info thread 441" (vedi sopra) e scoprirai che "Xorg" è il creatore del suddetto stacktrace, ma altrimenti, la maggior parte dei processi non usa FPU.

Da stacktrace, "get_sigframe ()" è la prima funzione che sembrava analizzare sull'utilizzo della 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;
}

Quindi essenzialmente ciò che sta accadendo qui è copiare le informazioni della FPU nel puntatore dello stack userspace (che è "sp").

Quindi, in sintesi, questa parte della logica di salvataggio / copia / ripristino della FPU viene attivata solo dopo l'utilizzo della FPU.

    
risposta data 16.12.2016 - 17:47
fonte
4

Many of these algorithms like registers like AVX and XMM registers, which I am not sure if the kernel ALWAYs reinitialize it from previous context (very performance-heavy if implemented)

Oh, ma lo fa. Almeno, per i processi che utilizzano SSE / XMM. L'accesso ai registri SSE / XMM può essere disabilitato nell'hardware (tramite CR4.OSFXSR), il che rende anche il passaggio delle attività più efficiente per i processi che non utilizzano affatto l'SSE / XMM.

And so if not, if one kernel module is doing the calculation, and then the intermediate keys can be sniffed from a usermode application looking at the XMM registers, is it not a risk?

Il kernel è molto attento a disinfettare tutto ciò che potrebbe essere stato toccato dal codice del kernel prima di tornare allo userspace.

Potrebbe non necessariamente salvarlo / ripristinarlo, in alcuni casi potrebbe solo cancellare con un valore zero. Ma tutto viene ripristinato o disinfettato.

Dove questo è fatto è una domanda diversa. L'ipotesi di default potrebbe essere che il kernel non tocchi registri così avanzati, quindi la responsabilità potrebbe essere dei moduli che li usano (almeno per le chiamate di sistema, il cambio di attività può essere leggermente più impegnato rispetto alle chiamate di sistema).

    
risposta data 16.12.2016 - 01:57
fonte
2

OK, passiamo al codice del kernel. Accetto in qualche misura con @DepressedDaniel che il kernel è probabilmente molto attento nella pulizia dei registri, l'interruttore di processo è un momento critico nel codice del kernel (probabilmente il momento più critico) quindi molta attenzione per rendere sicuro quel codice.

D'altra parte, i registri MMX / SSE / SSE2 / 3DNOW / MMXEXT sono registri in virgola mobile che non vengono spesso utilizzati. Metto tutte queste estensioni nella stessa barca dato che sono semplicemente registri lunghi insieme a una manciata di codici operativi per operare su di essi. Durante il processo di commutazione, il salvataggio e il ripristino di un gruppo di registri in virgola mobile che non vengono nemmeno utilizzati è un grosso problema di prestazioni, quindi il kernel non si preoccupa di farlo, a meno che non sia necessario. La domanda diventa quindi "Come il kernel sa che deve occuparsi dei registri MMX?" . Questo è qualcosa che dovrebbe essere presente in thread.flags , ci arriveremo a breve.

Dopo aver cercato i registri, ho scoperto che sono utilizzati in più posti di quanto pensassi all'inizio:

  • Il codice RAID utilizza MM0-MM7 e puoi persino vedere dove usa MM0-MM15 su macchine x86_64 (c'è anche una implementazione non SSE ).
  • Il codice 3DNOW di AMD è ancora nel kernel, sebbene io dubbio che sia usato su x86_64
  • KVM usa il sutff MMX, poiché potrebbe essere necessario emularlo
  • Abbastanza divertente, non ho trovato nessun MMX usato nei moduli di crittografia (non ho cercato troppo duramente).

La parte di cambio processo, al primo sguardo, non sembra utilizzare i registri MMX. Ma questo, ovviamente, non può essere vero poiché sappiamo bene che possiamo processare il processo RAID o KVM fuori dalla CPU e viceversa. La funzione principale del cambio di processo è (e praticamente lo è sempre stata) switch_to () . In là possiamo vedere:

#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)

E la struttura pt_regs non non contiene nulla su MM0-5, ma contiene materiale su MM6-11 (alla fine).

Quindi, cosa dà? Dove diavolo il kernel si occupa di altri registri? La chiave è la procedura __switch_to() (beh, macro, se vuoi essere pedante) che è un po 'più alto nel file sopra:

#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)

E all'interno ia64_save_extra() i flag del thread sono controllati e affrontato.

Nota inoltre che in ia64_fph_disable() sopra riportato sopra è sempre chiamato. Il codice per le altre procedure ia64_fph_* è appena sotto la macro disabilitata.

Il trucco finale sembra essere il fatto che su x86_64 i registri MM0-5 sono considerati volatili dalla CPU x86_64, quindi sono salvato insieme a RAX, RCX e RDX . Su semplice x86 questo è probabilmente fatto all'interno del codice che riguarda direttamente la roba MMX (e mi dispiace per il collegamento wikipedia, non ho trovato i documenti ABI).

    
risposta data 16.12.2016 - 16:15
fonte

Leggi altre domande sui tag