01 - Origine et motivation
Le problème de la sécurité mémoire en C/C++
Depuis des décennies, les langages C et C++ dominent les systèmes critiques (noyaux, navigateurs, firmwares, serveurs) pour leur performance et leur contrôle bas niveau. Ce contrôle a un prix : aucune barrière native n'empêche un pointeur de sortir de ses bornes légitimes. Selon une analyse de Microsoft portant sur douze années de CVE corrigées (MSRC, 2019) et une étude de l'équipe sécurité de Chrome portant sur 912 failles critiques corrigées depuis 2015 (Google Security Blog, 2021), environ 70% des vulnérabilités critiques corrigées dans ces bases de code sont des corruptions de mémoire : débordements de tampon, use-after-free, confusions de type. Ce chiffre décrit une tendance mesurée sur des périodes précises, pas une statistique universelle valable pour tout projet logiciel.
La réponse historique a été d'empiler des mitigations logicielles et matérielles partielles : canaries de pile, ASLR, NX/DEP, RELRO, CFI, Shadow Stacks. Nous avons détaillé dans nos articles sur le Jump-Oriented Programming et le Counterfeit Object-Oriented Programming comment chacune de ces protections, prise isolément, finit par être contournée par une combinaison de fuite d'information et de réutilisation de code. Ces mécanismes réduisent la surface d'attaque, ils ne l'éliminent pas : ils sont probabilistes (ASLR), partiels (NX ne protège pas contre le code-reuse) ou détectent la corruption après coup plutôt que de l'empêcher.
"Repousser indéfiniment les mêmes symptômes ne règle pas la maladie. Il fallait remonter à la source : le pointeur lui-même."
C'est ce constat qui a motivé, dès 2010, le projet CTSRD (Capsicum, TESLA and CHERI), une collaboration entre le Computer Laboratory de l'Université de Cambridge et SRI International, financée initialement par la DARPA dans le cadre des programmes CRASH puis SSITH (University of Cambridge, page du projet CTSRD/CHERI). L'objectif : reconcevoir le jeu d'instructions du processeur lui-même, pour que la notion de pointeur mémoire porte, nativement, la preuve de sa propre légitimité.
02 - Définition
Qu'est-ce que CHERI, précisément
CHERI (Capability Hardware Enhanced RISC Instructions) est une extension d'architecture de jeu d'instructions (ISA) qui introduit des capabilities matérielles : des pointeurs étendus, infalsifiables et vérifiés par le processeur à chaque déréférencement, portant leurs propres bornes mémoire, permissions d'accès et un bit de validité. CHERI étend un jeu d'instructions existant (MIPS, RISC-V, Arm) plutôt que de le remplacer, avec des modes de compatibilité, notamment hybrides, qui peuvent faciliter la migration de code existant. Les garanties de sécurité mémoire dépendent toutefois du mode d'exécution effectivement utilisé : code legacy non modifié, code hybride mêlant pointeurs classiques et capabilities, ou code recompilé en mode purecap, où pointeurs et conventions d'appel sont pleinement adaptés aux capabilities.
Cette définition tient en quelques lignes mais recouvre un changement de paradigme profond : au lieu de faire confiance au compilateur ou à des heuristiques runtime pour détecter une corruption, c'est le processeur qui refuse physiquement d'exécuter une instruction mémoire hors des droits accordés à la capability utilisée. La violation ne peut pas se produire silencieusement, elle est interceptée avant que le moindre octet ne soit écrit.
03 - Concept fondamental
La capability comme unité de confiance
Une capability CHERI encode simultanément quatre informations : une adresse (comme un pointeur classique), des bornes (base et longueur, définissant l'objet mémoire précis auquel elle donne accès), des permissions (lecture, écriture, exécution, capacité à charger ou stocker d'autres capabilities...) et un bit de tag qui indique si la capability est valide ou non.
Les trois garanties de sécurité
Trois propriétés, appliquées par le matériel à chaque instruction, forment le socle du modèle CHERI :
-
Provenance validity : une capability ne peut être créée que par dérivation d'une capability déjà valide (via des instructions comme
CSetBounds), ou par le matériel et le noyau au démarrage. Il est impossible de fabriquer une capability à partir d'un entier arbitraire. - Monotonicity : une capability dérivée ne peut que restreindre les droits de sa parente (bornes plus étroites, permissions retirées), jamais les élargir. Le principe du moindre privilège est appliqué mécaniquement, par construction.
- Tag unforgeability : chaque mot mémoire de la taille d'une capability est associé à un bit de tag hors bande, dans une mémoire dite "taguée". Toute écriture qui ne respecte pas le format capability (écriture partielle, octet par octet, opération arithmétique brute sur les bits encodés) efface ce bit automatiquement. Une capability dont le tag est à zéro redevient un simple entier inerte : toute tentative de l'utiliser comme pointeur déclenche une exception matérielle.
L'encodage réel sur une architecture CHERI 64-bit ("CHERI-128") compresse ces informations dans 128 bits via un format nommé CHERI Concentrate, auquel s'ajoute le bit de tag stocké séparément par le contrôleur mémoire. Une capability occupe donc deux fois la taille d'un pointeur classique en mémoire, un compromis assumé en échange des garanties apportées.
04 - Anatomie d'une capability
Les champs, visuellement
Capability CHERI - représentation conceptuelle (schématique, pas un format figé) [ Tag matériel, 1 bit ] hors-bande, stocké séparément par le contrôleur mémoire | +-- si TAG = 0 : capability invalide -> toute utilisation comme pointeur déclenche une exception matérielle (CHERI capability fault) [ Représentation de la capability, 128 bits sur CHERI-128 / CHERI Concentrate ] Address le "curseur" -- équivalent d'un pointeur classique Permissions R / W / X / Load-Cap / Store-Cap / Seal / Invoke ... Bounds base + longueur, encodage compressé (dépend de l'implémentation) Object Type scellée ou non (compartimentalisation) ... d'autres métadonnées existent selon l'architecture et la spécification # Note : schéma conceptuel. La disposition exacte des champs, leur taille # et leur encodage varient selon l'architecture CHERI (CHERI-128, Morello...) # et sa spécification. Le tag est toujours stocké séparément de la # représentation elle-même, jamais additionné dans un "total de bits" unique. # # Conséquence pratique : une capability occupe généralement le double de la # taille d'un pointeur classique équivalent (128 bits vs 64 bits), un coût # qui dépend aussi de l'alignement et des structures de données utilisées.
Les instructions clés de l'ISA CHERI
L'extension CHERI ajoute une famille d'instructions dédiées à la manipulation des capabilities (illustrées ici en CHERI-RISC-V simplifié, l'équivalent existe sur Arm Morello sous des mnémoniques proches). Chacune applique explicitement les trois garanties vues plus haut.
; Dériver une capability plus étroite (ex: allocation heap de "len" octets) CSetBounds c1, c0, len ; c1 = derive(c0), bornes = [addr(c0), addr(c0)+len] ; len hors de l'espace de c0 -> exception immédiate ; Introspection (lecture des champs, sans jamais permettre l'écriture directe) CGetBase rd, c1 ; rd = base de c1 CGetLen rd, c1 ; rd = longueur (bornes) de c1 CGetPerm rd, c1 ; rd = bitmask des permissions de c1 CGetTag rd, c1 ; rd = 1 si c1 est valide, 0 sinon ; Restreindre les permissions (monotonicity : jamais l'inverse) CAndPerm c2, c1, mask ; c2 = derive(c1), perm(c2) = perm(c1) AND mask ; Scellement : rend une capability non-déréférençable directement ; (base de l'object-capability pattern et de la compartimentalisation) CSeal c3, c1, c_otype ; c3 = c1 scellée sous le type c_otype CUnseal c4, c3, c_otype ; descellement, uniquement si c_otype correspond ; Transfert de contrôle sécurisé entre domaines de compartimentalisation CInvoke c_code, c_data ; saut + échange de capabilities scellées, atomique ; Toute instruction mémoire "classique" porte désormais un contrôle implicite Ld rd, 0(c1) ; charge depuis l'adresse de c1 ; vérifie : tag=1 ET in-bounds ET perm R, sinon fault Sd rs, 0(c1) ; stocke à l'adresse de c1 ; vérifie : tag=1 ET in-bounds ET perm W, sinon fault
05 - Modèle de sécurité (threat model)
Ce que CHERI garantit, et ce qu'il ne garantit pas seul
Voici un bilan honnête, dans le même esprit que nos analyses de protections logicielles : CHERI n'est pas une solution magique universelle, c'est une fondation matérielle sur laquelle d'autres mécanismes doivent parfois s'appuyer pour couvrir l'ensemble du spectre des vulnérabilités mémoire.
| Menace | Protection CHERI | Limitation / condition |
|---|---|---|
| Débordement spatial (hors des bornes) | Couverte | Dépend de l'autorité (bornes) de la capability effectivement utilisée pour l'accès. |
| Falsification de pointeur (pointer forgery) | Couverte | N'élimine pas un accès déjà rendu possible via une capability légitime détournée par ailleurs. |
| Sécurité temporelle (use-after-free) | Non couverte seule | Nécessite une stratégie de révocation dédiée (Cornucopia sur CheriBSD, barrière de chargement CHERIoT). |
| Détournement de flot de contrôle | Partielle | Dépend du modèle de contrôle de flot retenu et de l'implémentation exacte. |
| Compartimentalisation | Rendue possible | Le mécanisme existe (scellement, CInvoke), la politique de découpage reste à concevoir par le développeur. |
| Bug de logique métier | Non couverte | Contrôles applicatifs nécessaires ; hors du périmètre matériel de CHERI. |
| Canaux auxiliaires / DMA non contrôlé | Non couverte | Mitigations microarchitecturales spécifiques et IOMMU/architecture SoC complète à considérer séparément. |
Ces garanties concrètes dépendent de l'architecture CHERI considérée, du mode de compilation (purecap vs hybride), du système d'exploitation et des mécanismes additionnels effectivement activés.
CHERI déplace la ligne de défense au niveau le plus bas possible : le jeu d'instructions. Cela ferme silencieusement des classes entières de techniques d'exploitation, y compris celles décrites dans nos articles JOP et COOP, qui reposent toutes sur la capacité à fabriquer ou rediriger un pointeur à partir d'une fuite mémoire. Dans un environnement purecap correctement configuré, un leak d'adresse seul ne suffit généralement plus : les possibilités d'exploitation dépendent ensuite des capabilities déjà disponibles, des permissions en jeu, des interfaces de transition et de la présence d'autres primitives de corruption.
06 - Domaines d'application
Où CHERI change concrètement la donne
Le modèle CHERI n'est pas uniforme dans sa pertinence : certains domaines logiciels bénéficient immédiatement et directement de la sécurité spatiale matérielle, d'autres s'y intéressent surtout pour la compartimentalisation à bas coût qu'il rend possible. Voici les terrains où l'adoption est la plus étudiée ou la plus avancée.
- Systèmes embarqués et IoT : le terrain de prédilection de CHERIoT. Les microcontrôleurs pilotant des objets connectés tournent historiquement sur du C peu audité, avec des cycles de mise à jour rares. Des botnets comme Mirai ont exploité exactement ce type de faiblesses à grande échelle ; une sécurité mémoire appliquée au niveau matériel, sans réécriture du firmware existant, change directement la donne économique de ces attaques.
- Infrastructures critiques et systèmes industriels (OT/ICS) : ces environnements combinent contraintes temps-réel, cycles de vie de plusieurs décennies et impossibilité pratique de tout réécrire dans un langage memory-safe. CHERI ouvre une piste de durcissement qui préserverait le code C existant, mais son adoption en environnement OT reste à démontrer : compatibilité avec les processeurs et RTOS déjà déployés, déterminisme temps-réel, exigences de certification, disponibilité de l'outillage, et gestion des interfaces avec des composants non-CHERI et du DMA restent des questions ouvertes plutôt que des problèmes résolus.
- Navigateurs et moteurs de rendu : les parseurs de contenu non fiable (moteurs JavaScript, décodeurs image, PDF) concentrent une part disproportionnée des CVE critiques exploitées en conditions réelles, souvent via des techniques de code-reuse comme celles que nous avons couvertes dans nos articles JOP et COOP. C'est l'un des moteurs de l'intérêt de Google pour la sécurité mémoire matérielle.
-
Cloud et environnements multi-tenant :
CInvokefournit un mécanisme matériel pouvant faciliter une compartimentalisation à grain fin, en héritage direct des travaux Capsicum du même laboratoire de Cambridge. Le coût réel et le niveau d'isolation obtenus, comparés à une isolation par processus ou par conteneur, dépendent de l'architecture, du runtime et de la politique de sécurité retenue : une piste prometteuse, pas encore une comparaison chiffrée établie. - Automobile et avionique : secteurs régis par des normes de sûreté strictes (ISO 26262, DO-178C) et des bases de code C historiques impossibles à migrer rapidement. La convergence entre exigences de sûreté fonctionnelle et de cybersécurité pousse un intérêt naissant mais croissant pour une garantie matérielle plutôt que purement procédurale.
- Secteur défense et gouvernemental : l'origine même du financement DARPA et UKRI place naturellement les chaînes d'approvisionnement logicielles critiques de la défense parmi les premiers terrains d'évaluation, avec des exigences de traçabilité et de certification qui accompagnent déjà les programmes Morello.
Le fil conducteur entre ces domaines n'est pas la nature du logiciel, mais un même constat : une base de code C ou C++ ancienne, critique, trop volumineuse ou trop contrainte pour être réécrite intégralement dans un langage memory-safe, mais dont le coût d'une compromission mémoire reste inacceptable.
07 - Implémentations concrètes
De la recherche au silicium
CHERI n'est plus un concept purement académique. Plusieurs implémentations matérielles et logicielles existent aujourd'hui, à des stades de maturité très différents.
Arm Morello
Morello est un programme de recherche mené par Arm en collaboration avec l'Université de Cambridge, SRI International et l'UKRI, avec le soutien du gouvernement britannique. Il s'agit d'une extension expérimentale de l'architecture Armv8-A intégrant CHERI, matérialisée par une puce et une carte de développement distribuées dès 2022 à plus de 150 organisations (universités, industriels, agences gouvernementales). Arm est explicite sur son statut : Prototype de recherche, non destiné à une production commerciale en l'état.
Microsoft CHERIoT
CHERIoT (CHERI for embedded IoT) est une adaptation de Microsoft Research ciblant les microcontrôleurs bas coût, où chaque octet de RAM compte. Basé sur un cœur RISC-V Ibex modifié, CHERIoT ajoute la compartimentalisation à grain fin et une stratégie de révocation temporelle conçue pour en réduire fortement le coût, via un mécanisme de balayage en arrière-plan couplé à une barrière de chargement ; le coût réel dépend du modèle d'utilisation et de la charge. L'ensemble (cœur RTL, RTOS, toolchain) a été publié en open source en 2023, avec l'objectif explicite de protéger le code embarqué généralement peu audité qui pilote des objets connectés.
CheriBSD et les portages RISC-V
CheriBSD est un portage complet de FreeBSD exploitant CHERI : noyau, espace utilisateur et une bonne partie des paquets (via le projet CheriBSD Ports) recompilés en mode purecap. C'est l'environnement de référence pour expérimenter avec CHERI sur simulateur QEMU CHERI-RISC-V sans matériel Morello physique, ce que nous utilisons plus loin pour nos PoCs. Des portages CHERI-RISC-V existent également indépendamment de CheriBSD, visés par un groupe de travail au sein de RISC-V International.
Autres acteurs
SCI Semiconductor, spin-off issue directement des travaux de Cambridge, développe des produits commerciaux basés sur CHERIoT pour l'embarqué sécurisé. Google a publiquement soutenu les recherches sur la sécurité mémoire matérielle en tant que complément à ses investissements dans Rust et les langages memory-safe. La liste des organisations impliquées dans le programme d'accès technologique DSbD (voir section suivante) dépasse la centaine d'entités.
08 - Alliances et sponsors
Qui finance CHERI, et pourquoi
La trajectoire de CHERI, d'un papier de recherche à des puces distribuées à l'industrie, s'explique par une chaîne de financement et de soutien institutionnel inhabituellement large pour un projet d'architecture matérielle.
- DARPA (États-Unis) : financeur initial via les programmes CRASH puis SSITH (Cybersecurity and Assured Systems Engineering), qui visait explicitement à "éliminer" plutôt qu'à "atténuer" les classes de vulnérabilités matérielles.
- UKRI / Digital Security by Design (DSbD) : programme britannique lancé en 2019, financé à hauteur de 70 millions de livres par le gouvernement (Industrial Strategy Challenge Fund) et complété par au moins 117 millions de livres d'investissement industriel, soit environ 187 millions de livres au total (UKRI). Son volet matériel a directement soutenu la production des puces et cartes Morello ainsi que leur diffusion auprès d'un consortium de plus de 150 organisations.
- Arm : partenaire industriel qui a conçu et fabrique le silicium Morello, intégrant CHERI dans une variante expérimentale de son architecture Armv8-A.
- Microsoft : à travers Microsoft Research, sponsor et co-créateur de CHERIoT, avec publication en open source du cœur processeur, du RTOS et de la toolchain associée.
- Google : soutien financier et technique aux travaux de recherche sur la sécurité mémoire matérielle, en complément de ses initiatives internes sur les langages memory-safe.
- Université de Cambridge et SRI International : les deux institutions académiques à l'origine du projet CTSRD, qui continuent de porter la recherche fondamentale et la spécification formelle du modèle CHERI.
- CHERI Alliance : initiative industrielle dédiée à l'adoption de CHERI, rassemblant notamment Google, Microsoft, Arm, Wind River, Siemens, Codasip, VeriSilicon, les universités de Cambridge et d'Edimbourg, SRI International, ainsi que des agences gouvernementales britanniques (NCSC, DSTL) (cheri-alliance.org).
Cette alliance mêle financement public de recherche défense (DARPA), investissement industriel souverain (UKRI), et adoption par des acteurs commerciaux de premier plan (Arm, Microsoft, Google). C'est ce triptyque, rare dans le monde de l'architecture matérielle, qui explique pourquoi CHERI a dépassé le stade du papier académique.
09 - Mise en place de l'environnement de test
Reproduire les PoCs sans matériel Morello
Pas besoin d'une carte Morello physique pour expérimenter : le projet cheribuild automatise la construction d'un environnement complet CheriBSD riscv64-purecap, exécute sous QEMU avec support CHERI. C'est cet environnement qui sert de base aux PoCs des sections suivantes.
# Récupérer l'outil de build officiel du projet CTSRD-CHERI $ git clone https://github.com/CTSRD-CHERI/cheribuild $ cd cheribuild # Construire la toolchain CHERI-Clang/LLVM + un noyau CheriBSD purecap # pour riscv64 (premier build : plusieurs heures, compilation complète) $ ./cheribuild.py -d cheribsd-riscv64-purecap disk-image-riscv64-purecap # Lancer la machine virtuelle QEMU avec support CHERI activé $ ./cheribuild.py run-riscv64-purecap # Une fois dans le système, vérifier que l'extension est bien activée root@cheribsd:~ # sysctl hw.model hw.model: CHERI-RISC-V rv64gcxcheri (purecap)
Les PoCs de cet article (les trois variantes de compilation du PoC #1, et le PoC #2) ne sont pas illustratifs : ils ont été réellement compilés et exécutés pour cette publication, sur l'environnement suivant, construit intégralement depuis les sources.
- llvm-project (toolchain CHERI-Clang) : commit
7e122876ee - CheriBSD : FreeBSD 15.0-CURRENT, build
main-88f39900c329-dirty, noyauCHERI-PURECAP-QEMU - QEMU CHERI (fork CHERI-Alliance/qemu) : version 7.1.0, commit
fed6e523 - Cible : riscv64-purecap, révocation de capabilities (CHERI_CAPREVOKE) compilée dans le noyau mais non déclenchée automatiquement par l'allocateur par défaut
10 - PoC #1 : Sécurité spatiale en action
Un débordement de tampon trivial, deux issues radicalement différentes
Le code suivant contient un débordement de pile classique : aucune vérification de longueur avant la copie dans un tampon fixe. Compilons-le pour trois cibles : une architecture RISC-V standard sans CHERI, une architecture CHERI en ABI hybride (le matériel est disponible mais les pointeurs restent classiques par défaut), et le mode purecap, puis comparons les trois exécutions réelles.
#include <stdio.h> #include <string.h> void vuln(char *input) { char buf[16]; strcpy(buf, input); // aucune vérification de longueur printf("buf = %s\n", buf); } int main(int argc, char **argv) { if (argc < 2) return 1; vuln(argv[1]); return 0; }
# --- Non-CHERI : architecture classique, pointeurs 64-bit --- $ clang -target riscv64-unknown-freebsd -march=rv64gc -mabi=lp64d \ -O0 -o vuln_noncheri vuln.c # --- Hybride : matériel CHERI disponible, ABI à pointeurs classiques --- $ clang -target riscv64-unknown-freebsd -march=rv64gcxcheri -mabi=lp64d \ -O0 -o vuln_hybrid vuln.c # --- Purecap : capabilities partout, ABI CHERI --- $ clang -target riscv64-unknown-freebsd -march=rv64gcxcheri -mabi=l64pc128d \ -O0 -o vuln_purecap vuln.c # --- Exécution réelle sur CheriBSD 15.0-CURRENT / QEMU CHERI, charge de 40 octets --- $ ./vuln_noncheri AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped) # signal 11 : corruption silencieuse, crash tardif $ ./vuln_hybrid AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped) # signal 11 : identique au non-CHERI, l'ABI hybride # seule ne protège pas un char buf[16] classique $ ./vuln_purecap AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA In-address space security exception (core dumped) # signal 34 (SIGPROT)
La sortie brute suffit déjà à illustrer la différence. Voici la confirmation côté noyau, capturée via dmesg sur une CheriBSD 15.0-CURRENT réellement compilée et démarrée sous QEMU CHERI pour cet article (voir l'encart environnement ci-dessous pour les versions exactes) :
root@cheribsd-riscv64-purecap:~ # dmesg | tail -3 pid 1649 (vuln_noncheri), jid 0, uid 0: exited on signal 11 (core dumped) pid 1650 (vuln_hybrid), jid 0, uid 0: exited on signal 11 (core dumped) pid 1504 (vuln_purecap), jid 0, uid 0: exited on signal 34 (core dumped) # signal 11 = SIGSEGV (violation mémoire classique, détectée après le fait) # signal 34 = SIGPROT (faute de capability CHERI, bloquée au moment de l'écriture)
Voici le désassemblage réel du prologue de vuln() en purecap, obtenu avec llvm-objdump -d sur le binaire construit pour cet article :
# Sur l'hote Kali (SDK cross-compile, pas dans l'invite CheriBSD) : kali@kali:~/AMI/CHERI/poc $ ~/cheri/output/sdk/bin/llvm-objdump -d vuln_purecap | grep -A20 '<vuln>:' 0000000000001942 <vuln>: 1942: 1d 71 cincoffset csp, csp, -96 1944: 86 a8 sc cra, 80(csp) 1946: a2 a0 sc cs0, 64(csp) 1948: 80 10 cincoffset cs0, csp, 96 194a: db 05 a5 fe cmove ca1, ca0 194e: 5b 15 04 fc cincoffset ca0, cs0, -64 1952: 5b 25 05 01 csetbounds ca0, ca0, 16 # <-- la capability est bornee ici, exactement 1956: 23 48 a4 fa sc ca0, -80(cs0) 195a: 23 48 b4 fc sc ca1, -48(cs0) 195e: 8f 25 04 fd lc ca1, -48(cs0) 1962: 97 00 00 00 auipcc cra, 0 1966: e7 80 e0 0c jalr 206(cra) # appel a strcpy() 196a: 0f 25 04 fb lc ca0, -80(cs0) 196e: db 05 a1 fe cmove ca1, csp 1972: 88 a1 sc ca0, 0(ca1) 1974: 17 25 00 00 auipcc ca0, 2 1978: 0f 25 c5 18 lc ca0, 396(ca0) 197c: 97 00 00 00 auipcc cra, 0 1980: e7 80 40 0c jalr 196(cra) # appel a printf() 1984: c6 20 lc cra, 80(csp)
Le prologue confirme exactement le mécanisme en jeu : l'instruction csetbounds ca0, ca0, 16 (adresse 0x1952 dans notre binaire) restreint la capability de pile à exactement la taille réelle de buf (16 octets, pas un de plus). Le 17e octet écrit par strcpy vise donc une adresse hors de l'autorité de cette capability, d'où le SIGPROT immédiat. Résultat plus surprenant, et plus instructif : le binaire hybride (matériel CHERI présent, ABI à pointeurs classiques) plante exactement comme le binaire non-CHERI, signal 11 dans les deux cas. Dans ce PoC précis, le buffer est manipulé via des pointeurs classiques et la compilation en ABI hybride ne fournit aucune protection contre ce débordement particulier ; cela ne signifie pas que tout code hybride est dépourvu de tout mécanisme CHERI, seulement que rien ne protège automatiquement du code qui n'utilise pas explicitement de capabilities. Contrairement à un canary, qui au moins détecte la corruption au moment du ret, l'écriture fautive n'est ici bloquée par aucune vérification de bornes au moment où elle se produit.
Vérification au débogueur : la capability fautive, en direct
Pour aller au-delà de la sortie brute du shell, nous avons construit un GDB purecap depuis les sources (cible gdb-riscv64-purecap de cheribuild), capable de comprendre nativement le format capability, puis chargé les fichiers core générés automatiquement par chacun des trois crashs.
root@cheribsd-riscv64-purecap:~/poc # ../gdb -q vuln_purecap vuln_purecap.core Reading symbols from vuln_purecap... [New LWP 100093] Core was generated by `./vuln_purecap aaaa...a' (65 octets) Program terminated with signal SIGPROT, CHERI protection violation. Capability bounds fault caused by register ca2. #0 0x00000000402ef720 in strcpy (to=0x3fffdfff10 [rwRW,0x3fffdfff00-0x3fffdfff10] "\037\375\337\277?", from=0x3fbfdffd2f [rwRW,0x3fbfdffd1f-0x3fbfdffd89] 'a' <repeats 89 times>) at /home/kali/cheri/cheribsd/lib/libc/string/strcpy.c:48 (gdb) print $ca2 $1 = 0x3fffdfff0f [rwRW,0x3fffdfff00-0x3fffdfff10] # capability fautive : adresse hors bornes (gdb) print $ca0 $2 = 0x3fffdfff00 [rwRW,0x3fffdfff00-0x3fffdfff10] # capability de buf : bornes = 16 octets exactement (gdb) x/20cb 0x3fffdfff00 0x3fffdfff00: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 0x3fffdfff08: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 0x3fffdfff10: 31 '\037' -3 '\375' -33 '\337' -65 '\277' # donnees residuelles, pas notre payload
Cette dernière ligne est la preuve la plus directe qui soit : les 16 octets de buf contiennent bien nos 16 "a" légitimes, la copie a réussi jusque-là. Mais l'octet suivant, à l'adresse exacte où ca2 a déclenché la faute, contient encore des données résiduelles de pile, pas le 17e "a" de notre payload. Le matériel n'a pas seulement détecté le débordement, il a empêché l'écriture correspondante de se produire au tout premier octet en dehors des bornes.
C'est une confirmation directe, au registre près : le matériel identifie explicitement ca2 comme la capability fautive, avec des bornes [0x3fffdfff00-0x3fffdfff10], soit exactement 16 octets, identiques à celles de la capability originale de buf (ca0). Aucune place à l'interprétation : le CPU sait exactement quelle capability a été violée et où se trouvaient ses limites.
root@cheribsd-riscv64-purecap:~/poc # gdb -q vuln_noncheri vuln_noncheri.core Program terminated with signal SIGSEGV, Segmentation fault. Address not mapped to object. #0 0x00000000000119be in vuln () Backtrace stopped: Cannot access memory at address 0x6161616161616159 # 0x61 = 'a' en ASCII : l'adresse de retour de vuln() a ete ecrasee # par notre propre payload. Le crash survient au retour, pas a l'ecriture. root@cheribsd-riscv64-purecap:~/poc # gdb -q vuln_hybrid_true vuln_hybrid_true.core Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00000000000119c6 in vuln () Backtrace stopped: Cannot access memory at address 0x6161616161616159 # identique, au registre pres, au binaire non-CHERI : meme corruption, # meme adresse de retour ecrasee, meme absence totale de protection.
Cette comparaison au débogueur rend la différence tangible plutôt qu'affirmée : purecap identifie la capability précise et ses bornes exactes au moment de l'écriture fautive, tandis que non-CHERI et hybride partagent exactement la même signature de crash, un retour de fonction vers une adresse entièrement contrôlée par l'attaquant, l'un des symptômes les plus classiques d'un débordement de pile classique.
11 - PoC #2 : Falsification de pointeur
Quand un leak d'adresse parfait ne suffit plus
Toute la mécanique du JOP et du COOP, que nous avons détaillée dans nos articles précédents, repose sur une même prémisse : une fuite d'information (format string, heap leak, side-channel) livre une adresse numérique exacte, que l'attaquant réinjecte ensuite comme pointeur pour rediriger le flot de contrôle. Testons cette prémisse sur CHERI purecap.
#include <stdio.h> #include <string.h> #include <stdint.h> #include <cheriintrin.h> int main(void) { setvbuf(stdout, NULL, _IONBF, 0); // sortie non tamponnée : sinon perdue au SIGPROT /* Adresse "fuitée" par un bug indépendant (format string, heap leak...) */ uintptr_t raw_addr = 0x0000004012345000UL; /* Capability légitime avant toute manipulation : tag=1 */ void * __capability forged = &raw_addr; printf("[forge] avant écriture non-capability-aware :\n"); printf("[forge] tag : %d\n", cheri_tag_get(forged)); /* On copie ces 8 octets bruts dans l'emplacement mémoire d'une capability de 16 octets (128 bits) : exactement ce que ferait un attaquant qui rejoue un JOP/ROP classique après un leak */ memcpy(&forged, &raw_addr, sizeof(raw_addr)); // écriture non-capability-aware printf("[forge] après écriture non-capability-aware :\n"); printf("[forge] adresse encodée : 0x%lx\n", (uintptr_t)cheri_address_get(forged)); printf("[forge] tag : %d\n", cheri_tag_get(forged)); int value = *(volatile int * __capability)forged; // CHERI fault immédiat printf("jamais atteint : %x\n", value); return 0; }
root@cheribsd-riscv64-purecap:~/poc # ./forge [forge] avant écriture non-capability-aware : [forge] tag : 1 # capability légitime, encore intacte [forge] après écriture non-capability-aware : [forge] adresse encodée : 0x4012345000 [forge] tag : 0 # le memcpy() brut a détruit le tag In-address space security exception (core dumped) root@cheribsd-riscv64-purecap:~/poc # dmesg | tail -1 pid 1804 (forge), jid 0, uid 0: exited on signal 34 (core dumped) # le tag passe de 1 à 0 : l'adresse est correctement recopiée (0x4012345000), # memcpy() écrit les octets bruts sans passer par une instruction capability, # le contrôleur mémoire efface le bit de tag hors-bande associé à ce mot. # Le déréférencement qui suit déclenche SIGPROT avant toute lecture.
L'adresse numérique est correcte, parfaitement exacte, identique à celle qui aurait suffi sur une architecture classique. Elle ne sert pourtant à rien : le bit de tag, absent de la mémoire adressable conventionnelle, ne peut être positionné que par une instruction CHERI légitime opérant sur une capability parente déjà valide. C'est exactement la garantie de provenance validity à l'œuvre. Nuance importante pour la section suivante : ce résultat vaut pour du code compilé en mode purecap. Du code hybride, qui mélange volontairement pointeurs classiques et capabilities pour faciliter la migration progressive, peut réintroduire une fenêtre d'autorité ambiante si le portage n'est pas audité avec rigueur. Plus largement, CHERI complique ou empêche certaines étapes des chaînes JOP/COOP lorsqu'elles nécessitent la fabrication ou l'élargissement d'une capability, mais la faisabilité réelle d'une exploitation dépend toujours des capabilities déjà légitimement accessibles, des transitions hybrides et des autres primitives disponibles : ce n'est pas une fermeture totale et automatique du mécanisme.
12 - Limites et angles morts réels
Ce que CHERI ne résout pas
Présenter CHERI sans ses limites serait malhonnête. Voici les angles morts documentés, connus des chercheurs du domaine eux-mêmes.
-
Le fossé de la sécurité temporelle : sans mécanisme de révocation actif (Cornucopia sur CheriBSD, stratégie de révocation à coût réduit de CHERIoT via barrière de chargement), une capability vers un objet libéré reste utilisable. Nous l'avons vérifié directement : sur notre CheriBSD purecap, avec révocation compilée dans le noyau mais non déclenchée par l'allocateur par défaut, le tag d'une capability reste à 1 après
free(). La révocation à grande échelle reste une charge de recherche active, avec un coût runtime non négligeable selon l'implémentation. - Bugs de logique applicative : CHERI protège des objets mémoire, pas de la logique métier. Un contrôle d'accès manquant, une race condition / TOCTOU, ou un integer overflow qui fausse un calcul métier restent parfaitement invisibles pour CHERI dès lors que les accès mémoire eux-mêmes respectent les bornes et permissions accordées.
- Code legacy en mode hybride : CHERI propose un ABI hybride permettant de mélanger pointeurs classiques et capabilities pour une migration incrémentale. Tout code qui reste en mode legacy non-purecap n'est pas protégé, et les frontières purecap/hybride peuvent devenir des points de confusion si elles ne sont pas auditées.
- Canaux auxiliaires (side-channels) : CHERI ne mitige pas les attaques à exécution spéculative de classe Spectre, ni les canaux temporels ou de cache. Une vérification de capability qui échoue peut elle-même avoir des effets de bord microarchitecturaux exploitables.
- Base de confiance (TCB) : les garanties reposent sur un compilateur capability-aware correct et sur le noyau qui distribue les capabilities racines au démarrage et lors des appels système. Un bug dans cette base de confiance compromet tout l'édifice au-dessus.
- Acteurs non-CPU : des périphériques capables de DMA, des accélérateurs ou un cœur opérant en mode non-CHERI peuvent toujours écrire en mémoire brute sans passer par une vérification de capability, à moins que l'ensemble du SoC (IOMMU inclus) ne soit lui-même conçu CHERI-aware, ce qui reste rare en dehors des prototypes dédiés.
- Coût d'adoption : pointeurs doubles en taille, mémoire taguée nécessitant un contrôleur adapté, recompilation de la pile logicielle complète. C'est ce coût qui explique pourquoi CHERI reste, à ce jour, au stade du prototype (Morello) et du silicium embarqué de niche (CHERIoT) plutôt que du CPU grand public.
CHERI n'est pas la fin de la sécurité mémoire, c'est un changement de la ligne de départ. Il ferme des classes entières de vulnérabilités spatiales au niveau matériel, mais déplace la difficulté vers la sécurité temporelle, l'adoption écosystème et la confiance dans la base logicielle qui reste au-dessus du matériel.
13 - Questions et interrogations ouvertes
Ce que le débat de recherche n'a pas encore tranché
Au-delà des limites déjà documentées en section 12, certaines questions restent activement débattues dans la communauté de recherche et l'industrie, sans réponse définitive à ce jour.
Questions techniques non résolues
- Quel coût runtime pour une révocation généralisée ? Cornucopia sur CheriBSD et la barrière de chargement de CHERIoT prouvent la faisabilité de la sécurité temporelle, mais leur coût à l'échelle de charges de travail réelles (pas seulement des benchmarks académiques) reste une question ouverte.
- CHERI introduit-il de nouveaux gadgets spéculatifs ? L'extension n'a jamais prétendu couvrir les attaques de classe Spectre, mais la question de savoir si le mécanisme de vérification de capability lui-même crée de nouveaux canaux auxiliaires microarchitecturaux fait l'objet de recherches en cours.
- Combien de temps la période de transition hybride peut-elle durer ? Le mode hybride facilite la migration incrémentale, mais plus il persiste longtemps dans une base de code, plus les frontières purecap/hybride deviennent des points de confusion potentiels. Personne n'a de réponse définitive sur la durée acceptable de cette phase.
Questions d'adoption et d'économie
- Qui absorbe le coût de migration ? Recompiler des bases de code C/C++ massives (noyaux, navigateurs) en purecap est un effort non trivial, tout comme le doublement de la taille des pointeurs et le besoin d'une mémoire taguée compatible.
- CHERI atteindra-t-il un jour le silicium grand public ? Morello reste officiellement un véhicule de recherche : aucun engagement de production commerciale n'a été annoncé par Arm à ce jour.
- Quand la standardisation RISC-V aboutira-t-elle ? Le groupe de travail CHERI-RISC-V avance au sein de RISC-V International, mais la ratification finale et sa compatibilité avec d'autres extensions concurrentes ne sont pas encore tranchées.
Critiques de fond
- Le risque de faux sentiment de sécurité : certains chercheurs pointent qu'une couverture matérielle de la sécurité spatiale pourrait detourner l'attention d'autres classes de vulnérabilités (logique applicative, side-channels) qui restent, elles, entièrement exploitables.
- Une confiance déplacée plutôt qu'éliminée : les garanties de CHERI reposent in fine sur un compilateur et un noyau corrects. Le débat porte sur la difficulté réelle d'auditer cette base de confiance réduite, comparée à l'ancien modèle où tout était suspect par défaut.
- CHERI face aux langages memory-safe : l'effort d'ingénierie derrière CHERI (nouveau silicium, nouvelle toolchain, migration massive) est-il mieux investi que la réécriture progressive en Rust pour le code neuf ? La position dominante est que les deux approches sont complémentaires plutôt que concurrentes, CHERI pour le C legacy irréductible, Rust pour le code neuf, mais ce n'est pas un consensus universel.
14 - Le futur
Standardisation, silicium de production et questions ouvertes
Deux trajectoires parallèles dessinent l'avenir de CHERI. Côté matériel, un groupe de travail au sein de RISC-V International avance sur la standardisation d'une extension CHERI officielle pour l'ISA RISC-V (RISC-V Summit Europe, 2025) : un groupe de travail actif, pas encore une spécification ratifiée. Des cartes de développement commerciales existent déjà, comme la SONATA (lowRISC), et du silicium a été annoncé autour de la famille ICENI basée sur CHERIoT, ce qui ouvrirait la porte à une adoption par des fondeurs multiples plutôt qu'un seul acteur. Côté Arm, Morello reste officiellement désigné comme un véhicule de recherche : aucun engagement de mise en production commerciale n'a été annoncé, mais les enseignements tirés du programme continuent d'alimenter les discussions au sein d'Arm sur l'avenir de son architecture (statuts vérifiés en septembre 2026, susceptibles d'évoluer).
Plusieurs questions restent activement étudiées par la communauté de recherche : le passage à l'échelle de la révocation temporelle sans dégradation de performance perceptible, la maturité des compilateurs et de la chaîne d'outils purecap pour des bases de code massives (navigateurs, noyaux), la gestion de la fragmentation ABI entre code hybride et purecap durant une période de transition potentiellement longue, et enfin l'alignement des incitations économiques : qui absorbe le coût de migration d'une base de code existante vers CHERI. Ce dernier point est de plus en plus poussé par la pression réglementaire : les feuilles de route memory-safety publiées par plusieurs agences gouvernementales américaines, ainsi que le Cyber Resilience Act européen, déplacent progressivement cette question du terrain purement technique vers celui de la conformité.
15 - Références
- Watson, R.N.M. et al., "The CHERI Capability Model: Revisiting RISC in an Age of Risk", ISCA 2014
- Watson, R.N.M. et al., "CHERI: A Hybrid Capability-System Architecture for Scalable Software Compartmentalization", IEEE S&P 2015
- Woodruff, J. et al., "CHERI Concentrate: Practical Compressed Capabilities", IEEE Transactions on Computers, 2019
- Arm Limited, "Morello Program Overview", 2022
- Microsoft Research, "CHERIoT: Rethinking Security for Low-cost Embedded Systems", ASPLOS 2023
- UKRI / Digital Security by Design Programme, documentation publique du programme, ukri.org
- Miller, M. (Microsoft Security Response Center), "We need a safer systems programming language", 2019, microsoft.com/msrc
- Google Security Blog, "An update on Memory Safety in Chrome", 2021, security.googleblog.com
- University of Cambridge, Department of Computer Science and Technology, page du projet CTSRD/CHERI, cl.cam.ac.uk
- CHERI Alliance, présentation de l'initiative industrielle, cheri-alliance.org
- Kurd, T., "Standardizing CHERI-RISC-V", RISC-V Summit Europe, 2025, riscv-europe.org
- Projet
cheribuildet documentation CheriBSD, github.com/CTSRD-CHERI/cheribuild - Microsoft, CHERIoT-RTOS, github.com/microsoft/cheriot-rtos
- BUG|PWN, "Jump-Oriented Programming" et "Counterfeit Object-Oriented Programming", références croisées sur les techniques que CHERI neutralise