Les failles de sécurité SPECTRE et MELTDOWN sont, comme je l’ai expliqué dans des billets précédents, des attaques par canaux cachés. Il me semble pertinent de prendre ici un peu de recul sur ces questions ; je n’aurai pas la prétention de dire qu’il s’agit de philosopher, mais c’est un peu l’idée.

Grosso modo, quand on programme, on décrit une suite d’opérations à exécuter par la machine pour obtenir le résultat désiré. Le sens, on dit aussi la sémantique, de ces opérations est documenté, que l’on programme dans un langage de programmation de haut niveau (Python, C++, Prolog…) ou en langage machine (Intel x86…). Plus précisément, ce qui est documenté c’est la sémantique fonctionnelle, c’est-à-dire le résultat des opérations (par exemple, l’opération d’addition réalise une addition, sous réserve que le résultat ne dépasse pas un certain nombre de chiffres, etc.). Il existe en revanche diverses propriétés non fonctionnelles peu documentées et souvent difficilement prévisibles : le temps de calcul, la consommation électrique, etc. Naturellement, il est bien plus facile de raisonner sur les propriétés fonctionnelles que sur les propriétés non fonctionnelles.

Les mécanismes de sécurité incorporés aux langages, processeurs et systèmes d’exploitation modernes visent les propriétés fonctionnelles : notamment lorsqu’un programme tente d’accéder à des données auxquelles il n’a pas accès, cet accès est refusé. On peut ainsi isoler les uns des autres différents programmes, différents utilisateurs.

Il est toutefois bien plus difficile d’isoler les aspects non fonctionnels. Par exemple, si je lance un logiciel sur une machine, même si je n’ai pas officiellement le droit de lire la table des logiciels lancés par d’autres utilisateurs, je peux savoir qu’un autre logiciel est lancé si le mien est ralenti. Par des mesures plus fines, je peux même éventuellement me douter de ce qu’il fait : par exemple, s’il est dans une phase de calcul intensif sans grands accès à la mémoire principale, mes accès à la mémoire principale ne seront guère ralentis, tandis que s’il fait beaucoup d’accès, il ralentira les miens. D’une façon générale, plus il y a de ressources partagées (temps de calcul, mémoire, etc.) entre plusieurs utilisations, plus il y a la possibilité de passer des informations via des propriétés non fonctionnelles entre des domaines qui, a priori, sont fonctionnellement isolés. On appelle cela des canaux cachés.

Il y a maintes façons d’utiliser les canaux cachés. Par exemple, si on a la possibilité de faire fonctionner, sur la même machine, un logiciel qui a accès à une base de données sensibles mais n’a pas accès aux communications réseau et un logiciel qui n’a pas accès à la base de données et a accès aux communications réseau, séparés fonctionnellement (pas de canal de communication officiel entre les deux), on pourra transmettre des informations de l’un à l’autre par un canal caché (par exemple, mais il existe bien évidemment des méthodes plus efficaces, en alternant des périodes d’accès mémoire intensifs et faibles en une sorte de code Morse). La nouveauté des attaques MELTDOWN et SPECTRE est qu’il est possible de faire exécuter à un logiciel disposant d’accès privilégié aux données des instructions qu’il ne devrait pas vraiment exécuter, et qui n’ont d’ailleurs pas d’effet fonctionnel, mais conservent leurs effets non fonctionnels. Ce qui permet à ces attaques de fonctionner, c’est que diverses ressources matérielles (et notamment des mémoires cache) sont partagées entre des logiciels privilégiés (noyau du système d’exploitation, navigateur Web…) et non privilégiés (simple application, code Javascript d’une page Web…).

La complexité des machines actuelles, due à la recherche de hautes performance, avec la présence de nombreuses ressources partagées, est telle qu’il est difficile d’éviter les canaux cachés. Elle rend également difficile de démontrer quoi que ce soit de précis sur les propriétés non fonctionnelles, notamment le temps d’exécution. C’est un problème qui concerne notamment ceux qui veulent produire des dispositifs matériels répondant sous un certain délai garanti, par exemple des systèmes qui contrôlent des avions : comment, dans un système mettant en jeu plusieurs cœurs de processeur partageant de la mémoire, chacun avec des mémoires cache compliquées, garantir que l’exécution d’un programme prendra moins d’un centième de seconde dans tous les cas ? Nous sommes loin de ces processeurs des années 1980 dont la documentation donnait le temps d’exécution de chaque instruction...

Si l’on peut résumer une bonne partie de l’informatique depuis 70 ans, tant côté logiciel que matériel, on a progressivement éloigné le programmeur de l’exécution physique sur la machine, en rajoutant de plus en plus de couches de traduction, d’émulation, de virtualisation… Il y a d’excellentes raisons à cela : il est bon que le programmeur n’ait pas à se préoccuper des détails matériels de chaque machine, au risque que son programme ne fonctionne plus sur le modèle sorti un an après ; on peut mutualiser des ressources (dans un avion ou une voiture, mettre plusieurs tâches sur le même calculateur au lieu de mettre plusieurs calculateurs ; dans le cloud, mettre plusieurs utilisateurs indépendants sur le même serveur…). Cet éloignement nous empêche toutefois de bien appréhender les canaux cachés et propriétés non fonctionnelles.

Plus généralement, on a assisté à une bureaucratisation de la programmation. Sur un ordinateur personnel des années 1980, si l’on voulait dessiner une forme dans l’écran, il suffisait, au pire, d’aller écrire les valeurs des couleurs désirées dans la mémoire destinée à l’affichage vidéo : on voulait du bleu, on écrivait un code dans une case mémoire et on avait du bleu. Maintenant, il faut en quelque sorte remplir une série de formulaires administratifs pour avoir le droit de demander à un autre logiciel d’allumer le point. C’est la différence entre faire des travaux soi-même chez soi et demander au service de l’entretien d’envoyer un ouvrier les faire… Dans le premier cas, on maîtrise évidemment mieux ce qui est effectivement fait.