Concepts derrière l'ARMv8-M TrustZone
Utilisation de GCC CMSE (Cortex-M Security Extensions) avec les cœurs ARM Cortex-M33 et M23
Introduction & motivation
Cet article est le résultat de mes premiers pas avec le nouveau nRF9160 Système-en-package de Nordic Semiconductor. Ce SiP peut être utilisé pour créer des nœuds de capteurs IoT avec une connexion cellulaire à Internet. Il intègre un modem pour les nouvelles variantes à faible puissance "optimisées pour l'IoT" de la norme LTE, à savoir NB.IoT et LTE-M, qui vise à remplacer la connectivité M2M traditionnelle en utilisant les réseaux 2G.
Conceptuellement, le SiP est très intéressant pour nous à Lobaro car il rompt avec le concept général d'avoir un microprocesseur d'application qui parle sur la ligne série en utilisant des commandes AT avec un modem cellulaire séparé. En outre, il utilise le dernier noyau CPU ARM Cortex-M33 avec de nouvelles fonctionnalités de sécurité visant à sécuriser les applications IoT. Le modem LTE est directement intégré dans le SiP et communique avec le processeur d'application en utilisant la communication interprocesseur basée sur la mémoire partagée. Ce couplage étroit permet une meilleure optimisation de la faible consommation et des empreintes de PCB plus petites, tout en facilitant la sécurisation du nœud IoT.
Le processeur Cortex-M33 à l'intérieur du nRF9160 utilise la nouvelle architecture ARMv8-M qui offre une nouvelle fonctionnalité appelée "ARM TrustZone". L'article suivant reflète mon interprétation des concepts sous-jacents et de leur application pratique à l'aide du compilateur GNU ARM GCC et de ses fonctionnalités CMSE (=Cortex M Security Extensions). Comme il n'y a actuellement (mai 2019) pas beaucoup d'informations à trouver en ligne, j'espère que cet article pourra vous aider à vous lancer plus rapidement dans le sujet que je ne l'ai fait.
Bien que j'utilise le nRF9160 Cortex-M33 SiP, la plupart des concepts présentés ci-dessous sont également valables pour d'autres dispositifs ARMv8-M Cortex-M33 et Cortex-M23 de différents fournisseurs de silicium. Il en va de même pour l'utilisation d'autres compilateurs ARM que GCC.
Noyau Cortex-M33, (c) ARM®.
États d'exploitation des CPU sécurisés et non sécurisés
En plus des états d'exploitation classiques du CPU Thread (code normal) et Handler (IRQ/code d'exception), l'architecture ARMv8-M introduit des attributs sécurisés et non sécurisés. Après une réinitialisation, le CPU démarre toujours en mode sécurisé et peut accéder à toutes les régions de mémoire et utiliser n'importe quel périphérique sans restrictions. C'est comme le mode de fonctionnement normal des architectures plus anciennes comme le Cortex-M4 ou le Cortex-M3. Votre CPU basé sur ARMv8-M peut rester dans ce mode sans utiliser les fonctionnalités de TrustZone, mais la création d'applications sécurisées devient alors plus difficile. Vous devez doublement vérifier chaque partie de votre code, car s'il y a un bug, un attaquant (distant) peut avoir accès à l'ensemble de votre système et/ou à vos données d'identification stockées.
L'idée de la TrustZone est de cacher le code, les données et les périphériques relatifs à la sécurité dans une petite région de l'appareil. Cette région sera appelée "sécurisée" tandis que les autres parties de l'appareil seront traitées comme (potentiellement) "non sécurisées". La répartition des régions de mémoire, des interruptions et des périphériques en "sécurisé" et "non sécurisé" dépend de vous. Normalement, vos premières lignes de code après la mise sous tension prendront ces décisions en fonction des besoins de votre application. Dans le "Gestionnaire de partitions sécurisé"Code example for the nRF9160 Nordic decided to use a flash memory partition of 256kB secure and 768kB non-secure.
Le cœur du CPU maintient deux versions bancarisées (secure / non-secure) des registres pertinents pour l'exécution, par exemple les pointeurs de pile et le bloc de contrôle du système (SCB). Le mode de sécurité actuel du CPU définit la variante de registre qui sera utilisée. Le mécanisme de banque est présent de manière similaire pour certains périphériques (par ex. SysTick) qui sont implémentés deux fois dans le matériel. Le programme sécurisé peut toujours accéder aux deux versions. Après avoir défini le pointeur de pile non sécurisé et l'emplacement de l'adresse du vecteur non sécurisé, le code de démarrage sécurisé utilise un pointeur de fonction vers la table des vecteurs du micrologiciel non sécurisé. A partir de ce point, l'unité centrale exécute le micrologiciel non sécurisé.
Le firmware non sécurisé ne doit pas être conscient de l'existence du firmware sécurisé. Il peut être développé et déployé "normalement", comme sur les anciens cœurs, avec son script de liaison qui ne pointe que vers les régions de mémoire non sécurisées définies. Après avoir sauté dans le firmware non sécurisé, le CPU fonctionne en mode non sécurisé et ne peut plus accéder directement à un code, des données ou des périphériques sécurisés sans déclencher une exception de sécurité. La seule manière valable d'accéder à des fonctions sécurisées à partir de codes non sécurisés est d'invoquer les fonctions spéciales de passerelle / d'entrée / de façade que le côté sécurisé peut fournir. Le reste de cet article démontre en détail comment cela fonctionne en utilisant l'ARM GCC et son implémentation des CMSE (Cortex M Security-Extensions).
Régions de mémoire appelables non sécurisées (NSC) et instruction ASM de la passerelle sécurisée (SG)
Pour permettre des appels de fonctions non sécurisés -> sécurisés, une (petite) partie de la région flash sécurisée doit être configurée comme "Non-secure callable" (NSC) dans le code du firmware sécurisé. Sur le nRF9160, cela se fait à l'aide du périphérique SPU.
Comme décrit précédemment, le code non sécurisé ne peut pas appeler de fonctions dans le domaine de la mémoire sécurisée sans créer d'exception dans le noyau ARMv8-M. En marquant une petite partie de la mémoire sécurisée comme NSC, cette restriction est abaissée de telle sorte que les appels / branches dans des endroits contenant l'instruction d'assemblage "SG" (Secure Gateway) comme premier opcode sont désormais autorisés. A partir du point suivant l'instruction SG, le programme prendra une autre branche dans la mise en œuvre des fonctions sécurisées se trouvant dans la mémoire sécurisée plus restrictive, c'est-à-dire non marquée comme NSC. L'appel d'une fonction sécurisée à partir d'un code non sécurisé comme celui-ci est un processus en deux étapes avec des sauts en chaîne d'abord dans la mémoire marquée NSC (avec l'instruction SG) et ensuite un saut dans le corps de la fonction réelle en mémoire sécurisée. En utilisant ce processus, les entrées dans la mémoire sécurisée sont séparées du reste de la mémoire sécurisée. La raison pour laquelle il ne faut pas utiliser l'instruction SG directement au début des fonctions dans la mémoire sécurisée et éviter le processus en deux étapes est expliquée ici par le support technique ARM : Raisons de l'introduction des régions NSC.
Définition de fonctions appelables non sécurisées dans un micrologiciel sécurisé
Heureusement, les compilateurs ARMv8-M cachent au programmeur le processus en deux étapes décrit précédemment lors de l'appel d'une fonction sécurisée à partir du firmware non sécurisé. Une fonction sécurisée conçue comme point d'entrée pour un firmware non sécurisé doit être marquée avec l'attribut d'entrée non sécurisé après sa déclaration dans le firmware sécurisé :
// un fichier c du projet de firmware sécurisé définissant les fonctions de la passerelle veneer // doit être compilé avec -mcmse gcc flag ( !) #include "arm_cmse.h" __attribute__((cmse_nonsecure_entry)) void ControlCriticalIO(){ // faire quelques choses critiques }
Ceci instruit le compilateur de générer automatiquement deux parties de code pour le processus décrit ci-dessus si le calcul est effectué avec le "-mcmse" ARM gcc flag.
// Drapeaux GCC COMPILER utilisés lors de la construction du côté sécurisé arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]
Le corps de la fonction réelle sera placé par le linker dans la section .text sécurisée comme d'habitude, mais la partie avec les instructions SG et de branche sera placée dans une section spéciale appelée ".gnu.sgstubs" ou similaire pour les compilateurs C non GNU.
Les petites fonctions de redirection dans la région NSC sont également appelées fonctions "veneer" ou "SG stups", car elles sont si petites qu'elles ne font qu'une instruction SG et une branche. Le fichier de liaison du firmware sécurisé doit placer tout code de veneer dans la région de mémoire sécurisée que le firmware sécurisé déclarera comme NSC dans le cadre de son processus de démarrage sécurisé avant de sauter dans le firmware non sécurisé.
// Section de script de liaison pour les facettes de TrustZone Secure Gateway .gnu.sgstubs : ALIGN (32) { . = ALIGN(32) ; _start_sg = . ; *(.gnu.sgstubs*) . = ALIGN(32) ; _end_sg = . ; } > FLASH-REGION-WITH-NSC-ENABLED
Appel de fonctions sécurisées à partir de micrologiciels non sécurisés (NS->S)
Normalement, le firmware sécurisé sera développé et flashé indépendamment de l'image du firmware non sécurisé. Pour utiliser n'importe quelle fonctionnalité du firmware sécurisé, un fichier d'en-tête (.h) décrivant les fonctions appelables du côté sécurisé doit être inclus dans les sources non sécurisées. En plus de cela, le firmware non sécurisé doit savoir où les stubs de point d'entrée de veneer ont été placés avant dans les parties NSC de la mémoire sécurisée. Pour ce faire, il faut lier un fichier objet spécial (par exemple CMSE_importLib.o) au firmware non sécurisé contenant les informations d'adresse nécessaires. Ce fichier objet doit avoir été généré pendant le processus de liaison du firmware sécurisé à l'aide des options de liaison cmse suivantes :
// GCC LINKER flags utilisés lors de la liaison latérale SECURE arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignement [...].
L'option "-in-implib=CMSE_importLib.o" peut être utilisée pour conserver les entrées déjà définies dans les anciennes versions du fichier objet à l'emplacement exact de la mémoire, même dans la nouvelle version. Cela permet de faire des ajouts à l'image sécurisée sans avoir besoin de mettre à jour l'image non sécurisée également (qui peut avoir été liée à une version plus ancienne de la lib d'importation avant).
En utilisant le fichier .h et en le liant à l'objet CMSE_importLib.o, le firmware non sécurisé peut maintenant invoquer la fonction ControlCriticalIO() de l'exemple ci-dessus.
// Drapeaux GCC LINKER utilisés lors d'une liaison latérale non sécurisée arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]
Appel de fonctions non sécurisées (callback) à partir d'un firmware sécurisé (S->NS)
Cet article a démontré comment ARM TrustZone peut être utilisé pour créer deux parties de micrologiciel isolées, reliées par des fonctions de passerelle/voile miniatures bien définies. Celles-ci permettent d'appeler les fonctions du firmware sécurisé à partir du firmware non sécurisé.
L'autre sens, c'est-à-dire l'appel par le micrologiciel sécurisé d'une fonction non sécurisée, fonctionne si l'adresse de la fonction non sécurisée est d'une certaine manière donnée au code sécurisé. Normalement, cela se fait en définissant des fonctions de passerelle sécurisées avec un pointeur de rappel non sécurisé comme paramètre :
// un fichier c du projet de firmware sécurisé définissant les fonctions de la passerelle veneer // doit être compilé avec -mcmse gcc flag ( !) #include "arm_cmse.h" typedef void (*funcptr_ns) (void) __attribute__((cmse_nonsecure_call)) ; void ControlCriticalIO(funcptr_ns callback_fn) __attribute__((cmse_nonsecure_entry)){ funcptr_ns cb = callback_fn ; // save volatile pointer from non-secure code // vérifie si le pointeur donné vers la mémoire non-sécurisée est en fait non-sécurisé comme prévu cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE) ; if (cb != 0) { /* do some critical things e.g. use other secure functions */ cb() ; // invoke non-secure call back function }else { // ne fait rien si le pointeur est incorrect } }
L'attribut "nonsecure_call" est nécessaire pour instruire le compilateur sécurisé de clarifier les registres non-bancarisés à usage général avant de revenir dans le code non sécurisé, car ne pas le faire serait un risque potentiel pour la sécurité. En plus de cela, il demande au compilateur de clarifier le LSB de l'adresse des pointeurs de fonction dans l'instruction d'assemblage de branche sous-jacente. En faisant cela, le CPU passera de l'état sécurisé à l'état non sécurisé au sein de la branche.
Comme le callback non sécurisé donné doit être traité comme une variable volatile, dont le contenu peut être modifié par des interruptions non sécurisées, la copie du pointeur de fonction "cb" est nécessaire. Avant d'invoquer le callback, il faut vérifier que son pointeur ne pointe que vers la mémoire non sécurisée. S'il devait pointer vers une mémoire sécurisée, il y aurait également un risque de sécurité.
Registre de cœur Cortex-M33, (c) ARM®.
Conseils et autres lectures
N'oubliez pas que le script de liaison pour le côté sécurisé doit prendre en compte la section des fonctions de façade et que le script de liaison pour le côté non sécurisé doit refléter les partitions de mémoire faites dans le firmware sécurisé. Si les fonctions appelables non sécurisées sont incluses dans un fichier d'archive (*.a), elles doivent être liées au firmware sécurisé avec l'option de lien "whole-archive". Ce n'est que de cette manière que la lib d'importation CMSE inclura les fonctions de veneer nécessaires.
Quelques intrications de l'écriture du code sécurisé ARMv8-M - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
Trustzone avec ARMv8-m et le NXP lpc55s69-evk - Erich Styger
CM33 freeRTOS Exemple
written by Theodor Rohde (CEO of Lobaro GmbH) on May 4th 2019.
Salut Theodor,
Merci pour ce post et l'explication détaillée de l'architecture Armv8-M avec les extensions de sécurité. Btw, do you have any gcc-based projects on github related to this subject ?
Salutations,
Daniel
Bonjour Daniel,
nous prévoyons de publier un exemple minimal pour le nRF9160 en utilisant les extensions GCC CMSE et FreeRTOS. Il est prévu de l'utiliser comme environnement alternatif pour le SiP. Je pense que cela prendra environ 1 mois à partir de maintenant.
Greetings
Theo
Salut Theodor,
Après environ 20 ans de projection avec MSP430, j'ai voulu faire un changement.
Dans mon nouveau projet, je veux utiliser la technologie Trustzone et j'avais vraiment besoin de comprendre comment elle fonctionne.
Cette page est le seul endroit où le mode de fonctionnement est très simple et clairement décrit.
Salutations,
Neculai
Thank you :-)