Concepten achter de ARMv8-M TrustZone

GCC CMSE (Cortex-M Beveiligingsuitbreidingen) gebruiken met ARM Cortex-M33- en M23-kernen

Introductie & Motivatie

Dit artikel is het resultaat van mijn eerste stappen met de nieuwe nRF9160 systeem-in-pakket van Nordic Semiconductor. Deze SiP kan worden gebruikt om IoT-sensorknooppunten met cellulaire verbinding met het internet te maken. Het integreert een modem voor de nieuwe "voor IoT geoptimaliseerde" varianten met laag vermogen van de LTE-standaard, namelijk NB.IoT en LTE-M, die de traditionele M2M-connectiviteit met behulp van 2G-netwerken moet vervangen.

Conceptueel is de SiP erg interessant voor ons bij Lobaro omdat het breekt met het wijdverbreide concept van het hebben van een applicatie microprocessor die praat via de seriële lijn met behulp van AT-commando's met een aparte mobiele modem. Daarnaast maakt het gebruik van de nieuwste ARM Cortex-M33 CPU-kern met nieuwe beveiligingsfuncties die speciaal gericht zijn op veilige IoT-toepassingen. De LTE-modem is direct geïntegreerd in de SiP en communiceert met de applicatieprocessor via interprocessorcommunicatie op basis van gedeeld geheugen. Deze nauwe koppeling zorgt voor een betere optimalisatie van het lage stroomverbruik en kleinere PCB-voetafdrukken, naast een eenvoudigere beveiliging van het IoT-knooppunt.

De Cortex-M33-processor in de nRF9160 maakt gebruik van de nieuwe ARMv8-M-architectuur die een nieuwe functie biedt met de naam "ARM TrustZone". Het volgende artikel geeft mijn interpretatie weer van de onderliggende concepten en hun praktische toepassing met behulp van de GNU ARM GCC compiler en zijn CMSE (=Cortex M Security Extensions) functies. Aangezien er op dit moment (mei 2019) niet veel informatie online te vinden is, hoop ik dat dit artikel je kan helpen om een snellere start in het onderwerp te hebben dan ik had.

Hoewel ik de nRF9160 Cortex-M33 SiP gebruik, zijn de meeste concepten in het volgende ook geldig voor andere ARMv8-M Cortex-M33 en Cortex-M23 apparaten van verschillende silicium leveranciers. Hetzelfde geldt voor het gebruik van andere ARM-compilers dan GCC.

Kern Cortex-M33, (c) ARM®

Veilige en niet-veilige CPU-bedrijfsstatussen

Naast de klassieke Thread (normale code) en Handler (IRQ/Exception code) CPU-bedrijfstoestanden introduceert de ARMv8-M architectuur veilige en niet-veilige attributen. Na een reset start de CPU altijd in de veilige modus en heeft hij toegang tot alle geheugengebieden en kan hij elk randapparaat gebruiken zonder beperkingen. Dit is vergelijkbaar met de normale bedrijfsmodus van oudere architecturen zoals de Cortex-M4 of Cortex-M3. Je ARMv8-M gebaseerde CPU kan in deze modus blijven zonder de TrustZone functies te gebruiken, maar dan wordt het moeilijker om veilige toepassingen te maken. Je moet elk stukje van je code dubbel controleren, want als er een bug is, kan een (externe) aanvaller toegang krijgen tot je volledige systeem en/of opgeslagen referenties.

Het idee van de TrustZone is om beveiligingsrelevante code, gegevens en randapparatuur in een klein gebied van het apparaat te verbergen. Dit gebied wordt "veilig" genoemd terwijl de andere delen van het apparaat worden behandeld als (mogelijk) "niet-veilig". De verdeling van geheugenregio's, interrupts en randapparatuur in veilig en niet-veilig is aan jou. Normaal gesproken zullen de eerste regels code na het inschakelen deze beslissingen nemen op basis van de behoeften van de toepassing. In de "Beveiligde partitiemanager" codevoorbeeld voor de nRF9160 besloot Nordic om een flashgeheugenpartitie van 256 kB beveiligd en 768 kB niet-beveiligd te gebruiken.

De CPU-kern onderhoudt twee bankversies (beveiligd/niet-beveiligd) van registers die relevant zijn voor de uitvoering, zoals de stackpointers en het systeembesturingsblok (SCB). De huidige CPU beveiligingsmodus bepaalt welke registervariant wordt gebruikt. Het bankmechanisme is ook aanwezig voor sommige randapparaten (bijvoorbeeld SysTick) die twee keer in hardware geïmplementeerd zijn. Het veilige programma heeft altijd toegang tot beide versies. Na het instellen van de niet-veilige stackpointer en de locatie van het niet-veilige vectoradres gebruikt de veilige opstartcode een functiepointer naar de vectortabel van de niet-veilige firmware. Vanaf dit punt voert de CPU de niet-veilige firmware uit.

De niet-veilige firmware mag niet op de hoogte zijn van het bestaan van de veilige firmware. Deze kan "normaal" worden ontwikkeld en ingezet zoals op oudere cores, waarbij het linker-script alleen wijst naar de gedefinieerde niet-veilige geheugengebieden. Na het springen in de niet-veilige firmware draait de CPU in niet-veilige modus en heeft geen directe toegang meer tot beveiligde code, data of randapparatuur zonder een beveiligingsuitzondering te triggeren. De enige geldige manier die overblijft om toegang te krijgen tot veilige functies vanuit niet-veilige code is het aanroepen van speciale gateway / entry / veneer functies die de veilige kant kan bieden. De rest van dit artikel laat in detail zien hoe dit werkt met behulp van ARM GCC en zijn implementatie van de CMSE (Cortex M Security-Extensions).

Niet-beveiligde oproepbare (NSC) geheugengebieden en de beveiligde gateway (SG) ASM-instructie

Om niet-veilige -> veilige functie-aanroepen toe te staan moet een (klein) deel van het veilige flashgebied worden geconfigureerd als "Non-secure calllable" (NSC) in de veilige firmwarecode. Op de nRF9160 wordt dit gedaan met behulp van de SPU periferie.

Zoals eerder beschreven kan niet-veilige code geen functies in het beveiligde geheugendomein aanroepen zonder een uitzondering te maken in de ARMv8-M core. Door een klein deel van het beveiligde geheugen als NSC te markeren wordt deze beperking verlaagd, zodat aanroepen / vertakkingen naar locaties die de "SG" (Secure Gateway) assemblage instructie als eerste opcode bevatten nu zijn toegestaan. Vanaf het punt na de SG instructie zal het programma een andere vertakking nemen naar de implementatie van de beveiligde functies die zich in het veiliger geheugen bevinden, d.w.z. niet gemarkeerd als NSC. Het aanroepen van een beveiligde functie vanuit niet-veilige code zoals deze is een proces in twee stappen met geketende sprongen eerst in NSC-gemarkeerd geheugen (met de SG-instructie) en dan verder met een sprong naar de eigenlijke functie-inhoud in het beveiligde geheugen. Door dit proces te gebruiken worden de entries naar het beveiligde geheugen gescheiden van de rest van het beveiligde geheugen. De reden om de SG-instructie niet direct aan het begin van functies in het veilige geheugen te gebruiken en het tweestappenproces te vermijden, wordt hier uitgelegd door de technische ondersteuning van ARM: Redenen voor de invoering van de NSC-regio's.

Niet-beveiligde opvraagbare functies definiëren in beveiligde firmware

Gelukkig verbergen ARMv8-M compilers het eerder beschreven proces van twee stappen voor de programmeur bij het aanroepen van een beveiligde functie vanuit de niet-veilige firmware. Een veilige functie die is ontworpen als ingangspunt voor niet-veilige firmware moet worden gemarkeerd met het niet-veilige ingangskenmerk na de declaratie ervan in de veilige firmware:

// een c-bestand van een veilig firmwareproject dat veneer gateway-functies definieert
// moet gecompileerd worden met -mcmse gcc vlag (!)
#include "arm_cmse.h"
__attribute__((cmse_nonsecure_entry)) void ControlCriticalIO(){
// enkele kritieke dingen doen
}

Dit instrueert de compiler om automatisch twee delen code te genereren voor het hierboven beschreven proces als er gecompileerd wordt met de "-mcmse"ARM gcc vlag.

// GCC COMPILER vlaggen gebruikt tijdens het bouwen van de veilige kant
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

Het eigenlijke functiegedeelte wordt door de linker zoals gewoonlijk in de beveiligde .text-sectie geplaatst, maar het gedeelte met de SG- en vertakkingsinstructies wordt in een speciale sectie geplaatst met de naam ".gnu.sgstubs" of vergelijkbaar voor niet GNU C compilers.

De kleine redirect-functies in de NSC-regio worden ook wel "veneer"-functies of "SG-stups" genoemd, omdat ze zo klein zijn en slechts één SG-instructie en een vertakking uitvoeren. Het linkerbestand van de veilige firmware moet alle veneercode in de veilige geheugenregio plaatsen die de veilige firmware als NSC zal declareren als onderdeel van het veilige opstartproces voordat de niet-veilige firmware wordt opgestart.

// Linkerscript Sectie voor TrustZone Secure Gateway veneers
.gnu.sgstubs : ALIGN (32)
{
    . = ALIGN(32);
    _start_sg = ..;
    *(.gnu.sgstubs*)
    . = ALIGN(32);
    _end_sg = .;
} > FLASH-REGIO-MET-NSC-ENABLED

Beveiligde functies aanroepen vanuit niet-beveiligde firmware (NS->S)

Normaal gesproken wordt de veilige firmware onafhankelijk van het niet-veilige firmware-image ontwikkeld en geflasht. Om gebruik te kunnen maken van de functionaliteit van de veilige firmware moet een headerbestand (.h) met een beschrijving van de aanroepbare functies aan de veilige kant worden opgenomen in de niet-veilige broncode. Daarnaast moet de niet-veilige firmware weten waar de veneer entry point stubs eerder zijn geplaatst in de NSC-gedeelten van het beveiligde geheugen. Dit wordt gedaan door een speciaal objectbestand (bijvoorbeeld CMSE_importLib.o) te koppelen aan de niet-veilige firmware met de benodigde adresinformatie. Dit objectbestand moet zijn gegenereerd tijdens het koppelingsproces van de beveiligde firmware met behulp van de volgende cmse linkeropties:

// GCC LINKER vlaggen gebruikt tijdens SECURE side linking
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

De optionele "-in-implib=CMSE_importLib.o" kan gebruikt worden om entries die al gedefinieerd zijn in oudere versies van het objectbestand op exact dezelfde geheugenlocatie te houden als in de nieuwe versie. Dit maakt toevoegingen aan de beveiligde image mogelijk zonder dat ook de niet-beveiligde image hoeft te worden bijgewerkt (die mogelijk eerder is gekoppeld met een oudere versie van de import lib).

Door het .h-bestand te gebruiken en te linken tegen het CMSE_importLib.o object kan de niet-veilige firmware nu de ControlCriticalIO() functie uit het bovenstaande voorbeeld aanroepen.

// GCC LINKER vlaggen gebruikt tijdens niet-veilige zij-linking
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

Niet-beveiligde (callback) functies aanroepen vanuit beveiligde firmware (S->NS)

In dit artikel is gedemonstreerd hoe ARM TrustZone kan worden gebruikt om twee geïsoleerde firmware-onderdelen te maken die met elkaar verbonden zijn door goed gedefinieerde kleine gateway- / veneerfuncties. Hierdoor kunnen functies van de veilige firmware worden aangeroepen vanuit de niet-veilige firmware.

De andere richting, dat wil zeggen dat de veilige firmware een niet-veilige functie aanroept, werkt zonder meer als het adres van de niet-veilige functie op de een of andere manier aan veilige code wordt gegeven. Normaal gesproken wordt dit gedaan door veilige gatewayfuncties te definiëren met een niet-veilige callback pointer als parameter:

// een c-bestand van een veilig firmwareproject dat veneer gateway-functies definieert
// moet gecompileerd worden met -mcmse gcc vlag (!)
#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; // vluchtige pointer opslaan van niet-veilige code
 
 // controleer of gegeven pointer naar niet-veilig geheugen daadwerkelijk niet-veilig is zoals verwacht
 cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE);
  
 if (cb != 0) {
    /* kritieke dingen doen, bijvoorbeeld andere veilige functies gebruiken */
    cb(); // niet-veilige terugroepfunctie aanroepen
 }else {
   // niets doen als pointer onjuist is
 }
}

Het "nonsecure_call" attribuut is nodig om de veilige compiler te instrueren om de niet gebankte general purpose registers leeg te maken voordat er terug gesprongen wordt in de niet-veilige code, omdat het niet doen hiervan een potentieel veiligheidsrisico zou zijn. Daarnaast instrueert het de compiler om de LSB van het adres van de functiepunten in de onderliggende branch assembler instructie vrij te maken. Hierdoor gaat de CPU van een veilige naar een niet-veilige toestand binnen de branch.

Omdat de gegeven niet-veilige callback moet worden behandeld als een vluchtige variabele, waarvan de inhoud kan worden gewijzigd door niet-veilige interrupts, is de functie-pointer kopie "cb" nodig. Voordat de callback wordt aangeroepen, moet worden gecontroleerd of de pointer alleen naar niet-beveiligd geheugen wijst. Als het naar beveiligd geheugen zou wijzen, zou het ook een veiligheidsrisico zijn.

Cortex-M33 kernregister, (c) ARM®

Tips en verder lezen

Houd er rekening mee dat het linkerscript voor de veilige kant rekening moet houden met de sectie voor de veneerfuncties en dat het linkerscript voor de niet-veilige kant de geheugenpartities moet weergeven die in de veilige firmware zijn gemaakt. Als niet-veilige opvraagbare functies worden opgenomen in een archiefbestand (*.a), moet dit worden gekoppeld met de "whole-archive" koppelingsoptie naar de veilige firmware. Alleen op deze manier zal de CMSE import lib de benodigde veneer functies bevatten.

Enkele fijne kneepjes van het schrijven van veilige ARMv8-M-code - ARM Limited
SAM-L11-Verwijzing voor beveiliging-AN-DS70005365A.pdf - Microchip Technologie
Trustzone met ARMv8-m en de NXP lpc55s69-evk - Erich Styger
CM33 freeRTOS Voorbeeld

geschreven door Theodor Rohde (CEO van Lobaro GmbH) op 4 mei 2019.

4 antwoorden
  1. Daniel Oliveira zegt:

    Hoi Theodor,

    Bedankt voor dit bericht en de gedetailleerde uitleg over Armv8-M architectuur met beveiligingsuitbreidingen. Trouwens, heb je ook gcc-gebaseerde projecten op github die gerelateerd zijn aan dit onderwerp?

    Groetjes,
    Daniel

  2. Tobias zegt:

    Hallo Daniel,
    We zijn van plan om een minimaal voorbeeld voor de nRF9160 uit te brengen met behulp van GCC CMSE-uitbreidingen en FreeRTOS. Dit is bedoeld als alternatieve omgeving voor de SiP. Ik denk dat dit over ongeveer 1 maand zal gebeuren.

    Groeten
    Theo

  3. Neculai Agavriloaie zegt:

    Hoi Theodor,

    Na ongeveer 20 jaar projecteren met MSP430 wilde ik een verandering maken.
    In mijn nieuwe project wil ik Trustzone-technologie gebruiken en ik moest echt begrijpen hoe het werkt.

    Dit is de enige pagina waar de werking heel eenvoudig en duidelijk wordt beschreven.

    Groetjes,
    Neculai

Reacties zijn gesloten.