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

// DEFINITION_FORMELLE

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 :

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_layout.txt
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.

cheri_isa.s
; 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.

MenaceProtection CHERILimitation / 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.

// THREAT_MODEL_SUMMARY

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.

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.

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.

setup.sh
# 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)
// ENVIRONNEMENT_RÉEL_DE_CET_ARTICLE

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.

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.

vuln.c
#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;
}
build_compare.sh
# --- 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) :

dmesg.log
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 :

objdump_purecap.log
# 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.

gdb_purecap.log
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.

gdb_noncheri_hybride.log
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.

forge.c
#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;
}
./forge
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.

// BILAN_HONNETE

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

Questions d'adoption et d'économie

Critiques de fond

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