David Chisnall estime que le défi le plus difficile du cloisonnement logiciel ne consiste pas à séparer complètement deux charges de travail, mais à isoler des composants qui doivent communiquer et partager des données. Dans une présentation publiée par InfoQ, Chisnall explique comment l’architecture CHERI associe les concepts des capacités de sécurité à des pointeurs étendus afin de fournir une protection matérielle des pointeurs, tout en prenant en charge la sécurité mémoire et un cloisonnement précis au sein du même espace d’adressage.
Chisnall est cofondateur et directeur de l’ingénierie des systèmes chez SCI Semiconductor. Il est également chercheur invité à l’Université de Cambridge. La présentation s’appuie sur les travaux de recherche consacrés à CHERI et sur ses implémentations logicielles et matérielles, de CheriBSD et des extensions ARM Morello à CHERIoT, destiné aux microcontrôleurs, ainsi que sur la base RISC-V appelée RVY, dont il a déclaré que les travaux de standardisation étaient dans leur phase finale.
Le pointeur devient une entité protégée matériellement
CHERI ne fournit pas un jeu d’instructions autonome au sens traditionnel, mais un ensemble d’idées pouvant être adapté à différentes architectures, à l’image de la manière dont les concepts SIMD sont mis en œuvre au moyen de plusieurs extensions. Selon cette conception, le pointeur n’est plus un simple nombre représentant une adresse mémoire, mais devient une capacité contenant l’adresse, les limites de l’objet et un ensemble de permissions, ainsi qu’un marqueur matériel indiquant si la valeur est un pointeur valide.
Ces capacités imposent des propriétés unidirectionnelles : les limites peuvent être réduites et des permissions peuvent être supprimées, mais elles ne peuvent pas être étendues ni restaurées à partir du pointeur lui-même. Le matériel vérifie également que le pointeur est valide et marqué avant de l’utiliser pour des opérations de chargement, de stockage ou de saut. Si un programme tente d’utiliser une valeur traitée comme une donnée ou manipulée pour produire une adresse, l’opération échoue au lieu de la convertir automatiquement en pointeur utilisable.
Cette conception offre, selon la présentation, une protection directe contre plusieurs étapes courantes des attaques visant à exploiter la mémoire, comme l’écrasement d’un pointeur par une valeur contrôlée par l’attaquant. Elle permet également des opérations générales de copie telles que memcpy sans qu’il soit nécessaire de connaître à l’avance l’emplacement des pointeurs, car le marqueur du pointeur est transféré avec la valeur ou effacé lorsqu’une partie de l’espace de capacité est écrasée.
Des limites et des permissions pouvant être réduites
Les capacités CHERI comprennent des permissions de lecture et d’écriture, ainsi que des permissions distinctes pour manipuler les pointeurs présents dans les données. Chisnall explique que cette précision permet, par exemple, d’accorder à un programme la permission de lire les données d’une structure complexe sans lui donner la possibilité d’y stocker de nouveaux pointeurs. Il est ainsi possible de fournir une vue en lecture seule s’étendant à tout un arbre de données, plutôt que de protéger superficiellement un seul objet.
Le mécanisme de scellement permet également de créer des pointeurs opaques sécurisés, semblables à des handles ou à des descripteurs de fichiers, mais sans nécessiter obligatoirement une table de conversion gérée par le noyau. La capacité est scellée au moyen d’un code de type ; elle ne peut donc pas être modifiée, déréférencée ni utilisée pour effectuer des sauts, des chargements ou des stockages tant qu’elle est scellée. Lorsqu’elle est rendue à son propriétaire, le descellement ne réussit que si le code de type correspond.
Sécurité mémoire spatiale et temporelle
En cas d’erreurs de dépassement des limites, CHERI peut transformer un comportement indéfini du langage C en arrêt matériel déterministe. Chisnall présente l’exemple d’un tableau de 14 éléments dont on tente d’accéder à l’élément 14 : sur un système traditionnel, cet accès peut entraîner l’écrasement de l’adresse de retour, tandis que sur un système CHERI, il provoque une exception, car le pointeur est limité aux bornes du tableau.
La proposition ne se limite pas à la sécurité spatiale. Dans CHERIoT, les marqueurs de pointeurs et une mémoire fantôme contenant un bit par unité d’allocation sont utilisés afin d’invalider les pointeurs associés à une mémoire qui a été libérée. Lorsqu’une capacité est chargée dans un registre, le filtre de chargement peut vérifier l’état du bloc et effacer le marqueur si la mémoire a été libérée. Un mécanisme matériel périodique examine également la mémoire et retire les marqueurs des pointeurs qui désignent des objets qui ne sont plus vivants, ce qui permet de réutiliser la mémoire une fois le cycle d’invalidation terminé.
De la sécurité mémoire au cloisonnement précis
L’objectif initial du projet CHERI, tel que le présente Chisnall, était de construire un cloisonnement précis allant au-delà des limites du cloisonnement traditionnel entre processus. Isoler chaque composant dans un processus distinct entraîne des coûts liés aux tables de pages, aux mécanismes RPC, ainsi qu’à la sérialisation et à la copie des données. Cela peut être acceptable pour des dizaines de domaines isolés, mais devient moins adapté lorsque les navigateurs doivent, par exemple, exécuter des milliers ou des dizaines de milliers de bacs à sable pour des composants tels que le décodage des images et du son ou l’exécution du JIT JavaScript.
Le modèle CHERI permet de considérer l’ensemble de la mémoire accessible à partir des capacités présentes dans les registres d’exécution comme un domaine de protection. Deux domaines situés dans le même espace d’adressage peuvent partager certaines données tout en conservant d’autres parties isolées, car le partage commence à partir de capacités déterminées et peut s’étendre à des structures de données complexes ainsi qu’à tout ce qui est accessible depuis celles-ci.
Sur les systèmes de plus grande taille, la présentation explique une voie progressive au moyen de l’appel coexecve, qui crée un nouveau processus sans créer de nouvel espace d’adressage, puis installe un ensemble de capacités racines déterminant ce à quoi le nouveau processus peut accéder. CHERIoT adopte quant à lui une séparation stricte des permissions au sein d’un système d’exploitation temps réel, avec un composant de commutation chargé des changements de contexte et des appels entre compartiments, dont la taille est d’environ 350 instructions selon la présentation.
La conclusion de Chisnall est que la protection mémoire et le cloisonnement ne devraient pas dépendre d’une réécriture complète des règles du code ni de l’exécution de chaque composant dans un processus distinct. Préserver un modèle C proche de l’ordinaire, tout en rendant les pointeurs et les capacités d’accès compréhensibles et contrôlés par le matériel, pourrait offrir une voie progressive pour réduire les erreurs mémoire et construire un partage plus facilement auditable. Les détails de mise en œuvre restent différents entre les grands systèmes et les microcontrôleurs, et la présentation reflète le point de vue de son auteur ainsi que son expérience directe de CHERI, plutôt qu’une norme générale garantissant automatiquement l’élimination de tous les problèmes de sécurité.