Non ce n'est pas tout à fait cela. Aux premiers abords, ta version semble plus maline que ce qu'on lit ailleurs (où ils lisent bit par bit) car tu lis un octet d'un seul coup! Mais je pense que la raison pour laquelle ce n'est pas fait est que le processeur ne met pas seulement en cache une adresse accédée, mais aussi ses voisins (pour des raisons d'efficacité; comme tu le prouves toi-même, quand on accède à une valeur, on va probablement accéder à son voisin bientôt). Par conséquent:
Et si maintenant on fait une boucle sur mon_tableau
Ben là déjà, tu as corrompu les données. En lisant un index, tu mets en cache ses voisins et donc le compte du timing n'a plus de valeur pour les données voisines.
mon_tableau[mon_octet] = mon_tableau[mon_octet] * 42; // Ou toute autre opération sur mon_tableau[mon_octet]
En fait c'est là où est ton erreur. Faire une opération sur la valeur n'a absolument aucune sorte d'intérêt ici. Cette valeur va disparaîtra de toutes façons. En fait l'opération est faite sur l'index, déjà pour placer ton cache à des index connus (quelle que soit l'adresse lue), ensuite vraisemblablement pour placer les deux valeurs à comparer à des index bien séparés pour ne pas corrompre le cache. Je n'ai effectivement pas lu d'explication claire sur ce point précis, mais cela me semble la raison évidente pour laquelle toutes les explications font cela (autrement, pourquoi se faire chier et pas simplement faire un tableau avec 2 valeurs? Ben parce qu'elles seraient trop proche et en lire une mettrait vraisemblablement la seconde en cache immédiatement donc aucun calcul de timing possible!). Donc on lit en général des trucs du genre:
int x = mon_tableau[(mon_octet & 1) * 256];
Note qu'on s'en fout de la valeur x, qui peut d'ailleurs être locale (c'est juste histoire de); de même il n'est pas besoin d'écrire dans mon_tableau. On s'en fout, ça sera annulé. On veut juste lire son contenu, ce qui le mettra en cache (et ses voisins!). Et là, en fonction que le premier bit sera 0, ou 1, on mettra en cache l'index 0 ou 256! Et ce sont des index suffisamment éloignés pour ne pas craindre que le processeur ne mettra l'un en cache car il a lu l'autre.
Donc non, ce n'est pas n'importe quelle opération qu'il faut faire, mais une opération faite pour éloigner les 2 index possibles et avoir des index connus (on ne veut surtout pas avoir à "chercher" car ça corromprait le cache et rendrait le calcul de timing inadéquat).
Bien sûr, pour lire le second bit, il faut tout refaire, et la fois suivante, construire l'index ainsi:
int x = mon_tableau[((mon_octet >> 1) & 1) * 256];
Ce qui encore une fois mettra en cache soit l'index 0 (et voisins) soit l'index 256 (et voisins).
Et ainsi de suite... En juste 8 boucles de ce code (en paramétrant le décalage), on a lu un octet complet.
Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]
[^] # Re: Comment le cache peut être lu ?
Posté par Jehan (site web personnel, Mastodon) . En réponse à la dépêche Deux failles critiques : Meltdown et Spectre. Évalué à 8.
Non ce n'est pas tout à fait cela. Aux premiers abords, ta version semble plus maline que ce qu'on lit ailleurs (où ils lisent bit par bit) car tu lis un octet d'un seul coup! Mais je pense que la raison pour laquelle ce n'est pas fait est que le processeur ne met pas seulement en cache une adresse accédée, mais aussi ses voisins (pour des raisons d'efficacité; comme tu le prouves toi-même, quand on accède à une valeur, on va probablement accéder à son voisin bientôt). Par conséquent:
Ben là déjà, tu as corrompu les données. En lisant un index, tu mets en cache ses voisins et donc le compte du timing n'a plus de valeur pour les données voisines.
En fait c'est là où est ton erreur. Faire une opération sur la valeur n'a absolument aucune sorte d'intérêt ici. Cette valeur va disparaîtra de toutes façons. En fait l'opération est faite sur l'index, déjà pour placer ton cache à des index connus (quelle que soit l'adresse lue), ensuite vraisemblablement pour placer les deux valeurs à comparer à des index bien séparés pour ne pas corrompre le cache. Je n'ai effectivement pas lu d'explication claire sur ce point précis, mais cela me semble la raison évidente pour laquelle toutes les explications font cela (autrement, pourquoi se faire chier et pas simplement faire un tableau avec 2 valeurs? Ben parce qu'elles seraient trop proche et en lire une mettrait vraisemblablement la seconde en cache immédiatement donc aucun calcul de timing possible!). Donc on lit en général des trucs du genre:
Note qu'on s'en fout de la valeur x, qui peut d'ailleurs être locale (c'est juste histoire de); de même il n'est pas besoin d'écrire dans
mon_tableau. On s'en fout, ça sera annulé. On veut juste lire son contenu, ce qui le mettra en cache (et ses voisins!). Et là, en fonction que le premier bit sera 0, ou 1, on mettra en cache l'index 0 ou 256! Et ce sont des index suffisamment éloignés pour ne pas craindre que le processeur ne mettra l'un en cache car il a lu l'autre.Donc non, ce n'est pas n'importe quelle opération qu'il faut faire, mais une opération faite pour éloigner les 2 index possibles et avoir des index connus (on ne veut surtout pas avoir à "chercher" car ça corromprait le cache et rendrait le calcul de timing inadéquat).
Bien sûr, pour lire le second bit, il faut tout refaire, et la fois suivante, construire l'index ainsi:
Ce qui encore une fois mettra en cache soit l'index 0 (et voisins) soit l'index 256 (et voisins).
Et ainsi de suite... En juste 8 boucles de ce code (en paramétrant le décalage), on a lu un octet complet.
Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]