Le premier utilisant la faille 1 permet de lire la mémoire sans sortir de son process ni de violer des privilèges. Il fonctionne sur des CPU AMD, Intel et ARM.
Le deuxième utilisant la faille 1 permet de lire la mémoire kernel à un partir du userspace. Il fonctionne sur des CPU Intel. Pour AMD, il faut qu'il soit dans un mode non standard (Activation du BPF JIT).
Le PoC pour la faille 2 permet, en étant root dans un guest KVM, de lire la mémoire kernel du host. Il a été testé sur CPU Intel et avec une vieille version d'un kernel debian.
Le PoC pour la faille 3 permet de lire la mémoire kernel à partir d'un process user. Cela fonctionne sur CPU Intel et avec certaines conditions (la mémoire doit être dans le cache L1D).
Pour être franc, je n'ai pas compris tous les détails de ces PoC, mais ils semblent être toutes construits de la même façon. Je vais donc parler du premier, qui semble le plus simple à comprendre. Le PoC semble être quasiment le même mais au niveau assembleur plutôt qu'en C.
Le code en question est :
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
unsigned long untrusted_offset_from_caller = ...; /* >0x400 (OUT OF BOUNDS!) */
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
}
En gros, dans un bloc conditionnel qui sera toujours faux, une demande de lire un bit de la mémoire en faisant un débordement de buffer. Puis en fonction du résultat de la lecture, on va écrire dans un buffer accessible par l'utilisateur dans un offset différent suivant si c'est un 0 ou 1.
Dans un second temps (non montré dans le code), on va tester la vitesse de lecture de ce buffer au deux offsets. Celui dans la lecture est le plus rapide indiquera la valeur du bit lu précédemment.
Comment cela fonctionne ? En gros, le CPU va penser que le bloc conditionnel sera vrai, et donc va chercher à l'évaluer en avance de phase. C'est de l'Exécution_spéculative et cela fait gagner du temps au CPU. En fonction de la valeur lue, on va écrire la donnée dans un offset particulier d'un tableau. Cela va donc charger cette case mémoire en cache. Quand le CPU va voir que la condition est fausse, il va donc défaire les calculs qu'il a déjà fait, l'offset du tableau ne contiendra pas la valeur calculée, néanmoins il restera en cache.
Quand on cherchera a tester les temps d'accès au offsets en question, celui chargé en mémoire sera plus rapide, et donc on pourra savoir qu'elle est la valeur lue.
Ce PoC ne sert pas à grand chose, mais permet de comprendre comme la gamme de failles fonctionne.
Dans une branche qui ne doit pas être exécutée, mais que le CPU exécutera "au cas ou", on essayer d'accéder à de la mémoire qu'on ne doit pas pouvoir lire. Même si cette exécution sera défaite par la suite, on cherche un moyen de connaitre la valeur de la lecture en utilisant un canal caché (présence d'objet dans un cache par exemple).
# Détails techniques
Posté par TilK . En réponse au journal Ça sent pas bon chez Intel ?. Évalué à 10.
Les détails techniques ont été publiés par Project Zero, ici et là.
Pour les décideurs pressés, il semble y avoir 3 failles :
Les deux premières ont été nommés Spectre et la troisième Meltdown.
L'équipe a réalisé 4 PoC :
Pour être franc, je n'ai pas compris tous les détails de ces PoC, mais ils semblent être toutes construits de la même façon. Je vais donc parler du premier, qui semble le plus simple à comprendre. Le PoC semble être quasiment le même mais au niveau assembleur plutôt qu'en C.
Le code en question est :
En gros, dans un bloc conditionnel qui sera toujours faux, une demande de lire un bit de la mémoire en faisant un débordement de buffer. Puis en fonction du résultat de la lecture, on va écrire dans un buffer accessible par l'utilisateur dans un offset différent suivant si c'est un 0 ou 1.
Dans un second temps (non montré dans le code), on va tester la vitesse de lecture de ce buffer au deux offsets. Celui dans la lecture est le plus rapide indiquera la valeur du bit lu précédemment.
Comment cela fonctionne ? En gros, le CPU va penser que le bloc conditionnel sera vrai, et donc va chercher à l'évaluer en avance de phase. C'est de l'Exécution_spéculative et cela fait gagner du temps au CPU. En fonction de la valeur lue, on va écrire la donnée dans un offset particulier d'un tableau. Cela va donc charger cette case mémoire en cache. Quand le CPU va voir que la condition est fausse, il va donc défaire les calculs qu'il a déjà fait, l'offset du tableau ne contiendra pas la valeur calculée, néanmoins il restera en cache.
Quand on cherchera a tester les temps d'accès au offsets en question, celui chargé en mémoire sera plus rapide, et donc on pourra savoir qu'elle est la valeur lue.
Ce PoC ne sert pas à grand chose, mais permet de comprendre comme la gamme de failles fonctionne.
Dans une branche qui ne doit pas être exécutée, mais que le CPU exécutera "au cas ou", on essayer d'accéder à de la mémoire qu'on ne doit pas pouvoir lire. Même si cette exécution sera défaite par la suite, on cherche un moyen de connaitre la valeur de la lecture en utilisant un canal caché (présence d'objet dans un cache par exemple).