Koncepter bag ARMv8-M TrustZone

Brug af GCC CMSE (Cortex-M Security Extensions) med ARM Cortex-M33- og M23-kerner

Introduktion og motivation

Denne artikel er resultatet af mine første skridt med den nye nRF9160 System-i-pakke fra Nordic Semiconductor. Denne SiP kan bruges til at skabe IoT-sensornoder med mobilforbindelse til internettet. Den integrerer et modem til de nye "IoT-optimerede" lavenergivarianter af LTE-standarden, nemlig NB.IoT og LTE-M, som er beregnet til at erstatte traditionel M2M-forbindelse ved hjælp af 2G-netværk.

Konceptuelt er SiP'en meget interessant for os hos Lobaro, fordi den bryder med det udbredte koncept med at have en applikationsmikroprocessor, der taler over en seriel linje ved hjælp af AT-kommandoer med et separat mobilmodem. Derudover bruger den den nyeste ARM Cortex-M33 CPU-kerne med nye sikkerhedsfunktioner, der er rettet mod særligt sikre IoT-applikationer. LTE-modemet er direkte integreret i SiP'en og kommunikerer med applikationsprocessoren ved hjælp af interprocessor-kommunikation baseret på delt hukommelse. Denne tætte kobling giver mulighed for bedre optimering af lavt strømforbrug og mindre PCB-fodaftryk foruden lettere sikring af IoT-noden.

Cortex-M33-processoren i nRF9160 bruger den nye ARMv8-M-arkitektur, som tilbyder en ny funktion kaldet "ARM TrustZone". Den følgende artikel afspejler min fortolkning af de underliggende koncepter og deres praktiske anvendelse ved hjælp af GNU ARM GCC-compileren og dens CMSE-funktioner (=Cortex M Security Extensions). Da der i øjeblikket (maj 2019) ikke er meget information at finde online, håber jeg, at denne artikel kan hjælpe dig med at få en hurtigere start på emnet, end jeg havde.

Selvom jeg bruger nRF9160 Cortex-M33 SiP, er de fleste koncepter i det følgende også gyldige for andre ARMv8-M-aktiverede Cortex-M33- og Cortex-M23-enheder fra forskellige siliciumleverandører. Det samme gælder for brug af andre ARM-compilere end GCC.

Cortex-M33-kerne, (c) ARM®.

Sikre og ikke-sikre CPU-driftstilstande

Ud over de klassiske CPU-driftstilstande Thread (normal kode) og Handler (IRQ/Exception-kode) introducerer ARMv8-M-arkitekturen sikre og ikke-sikre attributter. Efter reset starter CPU'en altid i sikker tilstand og kan få adgang til alle hukommelsesområder og bruge alle perifere enheder uden begrænsninger. Dette svarer til den normale driftstilstand for ældre arkitekturer som Cortex-M4 eller Cortex-M3. Din ARMv8-M-baserede CPU kan forblive i denne tilstand uden at bruge TrustZone-funktionerne, men så bliver det sværere at skabe sikre applikationer. Du er nødt til at dobbelttjekke hver eneste del af din kode, for hvis der er en fejl, kan en (fjern)angriber få adgang til hele dit system og/eller gemte legitimationsoplysninger.

Ideen med TrustZone er at skjule sikkerhedsrelevant kode, data og periferiudstyr i et lille område af enheden. Dette område vil blive kaldt "sikkert", mens de andre dele af enheden vil blive behandlet som (potentielt) "ikke-sikkert". Opdelingen af hukommelsesregioner, interrupts og perifere enheder i sikre og ikke-sikre er op til dig. Normalt vil dine første kodelinjer efter opstart udføre disse beslutninger baseret på dine applikationsbehov. I "Sikker partitionshåndtering" kodeeksempel for nRF9160 Nordic besluttede at bruge en flash-hukommelsespartition på 256kB secure og 768kB non-secure.

CPU-kernen opretholder to bankede versioner (sikker/ikke-sikker) af eksekveringsrelevante registre, f.eks. stakpointerne og systemkontrolblokken (SCB). Den aktuelle CPU-sikkerhedstilstand definerer, hvilken registervariant der skal bruges. Bankmekanismen er ligeledes til stede for nogle perifere enheder (f.eks. SysTick), som er implementeret to gange i hardware. Det sikre program kan altid få adgang til begge versioner. Efter opsætning af den ikke-sikre stack-pointer og placeringen af den ikke-sikre vektoradresse bruger den sikre boot-kode en funktionspointer til vektortabellen i den ikke-sikre firmware. Fra dette punkt kører CPU'en den ikke-sikre firmware.

Den ikke-sikre firmware må ikke være klar over, at den sikre firmware findes. Den kan udvikles og implementeres "normalt" som på ældre kerner med sit linker-script, der kun peger på de definerede ikke-sikre hukommelsesregioner. Efter at have hoppet ind i den ikke-sikre firmware kører CPU'en i ikke-sikker tilstand og kan ikke længere få direkte adgang til sikker kode, data eller periferienheder uden at udløse en sikkerhedsundtagelse. Den eneste gyldige måde, hvorpå man kan tilgå sikre funktioner fra ikke-sikker kode, er ved at påkalde særlige gateway-/indgangs-/finérfunktioner, som den sikre side kan tilbyde. Resten af denne artikel demonstrerer, hvordan dette fungerer i detaljer ved hjælp af ARM GCC og dens implementering af CMSE (Cortex M Security-Extensions).

Non-secure callable (NSC) hukommelsesregioner og secure gateway (SG) ASM-instruktionen

For at tillade ikke-sikre -> sikre funktionskald skal en (lille) del af det sikre flashområde konfigureres som "Non-secure callable" (NSC) i den sikre firmwarekode. På nRF9160 gøres dette ved hjælp af SPU-periferien.

Som beskrevet før kan ikke-sikker kode ikke kalde funktioner i det sikre hukommelsesdomæne uden at udløse en undtagelse i ARMv8-M-kernen. Ved at markere en lille del af den sikre hukommelse som NSC sænkes denne begrænsning på den måde, at kald/forgreninger til placeringer, der indeholder "SG" (Secure Gateway) assembly-instruktionen som første opcode, nu er tilladt. Fra punktet efter SG-instruktionen vil programmet tage endnu en forgrening ind i den sikre funktionsimplementering, der ligger i den mere begrænsede sikre hukommelse, dvs. ikke markeret som NSC. At kalde en sikker funktion fra ikke-sikker kode som denne er en totrinsproces med kædede spring først ind i NSC-markeret hukommelse (med SG-instruktion) og derefter videre med et spring ind i den faktiske funktionskrop i sikker hukommelse. Ved at bruge denne proces adskilles indgangene til den sikre hukommelse fra resten af den sikre hukommelse. Grunden til ikke at bruge SG-instruktionen direkte i begyndelsen af funktioner i den sikre hukommelse og undgå totrinsprocessen er forklaret af ARM's tekniske support her: Årsager til indførelsen af NSC-regionerne.

Definition af ikke-sikre kaldbare funktioner i sikker firmware

Heldigvis skjuler ARMv8-M compilere den tidligere beskrevne to-trins proces for programmøren, når der kaldes en sikker funktion fra den ikke-sikre firmware. En sikker funktion, der er designet som entry point for ikke-sikker firmware, skal markeres med attributten nonsecure entry efter dens deklaration i den sikre firmware:

// en c-fil fra secure firmware-projektet, der definerer veneer-gateway-funktioner
// skal kompileres med -mcmse gcc-flag (!)
#include "arm_cmse.h"
__attribute__((cmse_nonsecure_entry)) void ControlCriticalIO(){
// gør nogle kritiske ting
}

Dette instruerer compileren i automatisk at generere to kodedele til den ovenfor beskrevne proces, hvis den kompilerer med "-mcmse"ARM gcc-flag.

// GCC COMPILER-flag brugt under opbygning af sikker side
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

Selve funktionskroppen vil blive placeret af linkeren i den sikre .text-sektion som normalt, men delen med SG- og branch-instruktionerne vil blive placeret i en særlig sektion kaldet ".gnu.sgstubs" eller lignende for ikke GNU C-kompilere.

De små redirect-funktioner i NSC-regionen kaldes også "veneer"-funktioner eller "SG stups", fordi de er så små, at de kun udfører en SG-instruktion og en branch. Linkerfilen til den sikre firmware skal placere enhver finérkode i den sikre hukommelsesregion, som den sikre firmware vil erklære som NSC som en del af sin sikre opstartsproces, før den hopper ind i den ikke-sikre firmware.

// Linkerscript-sektion til TrustZone Secure Gateway-finér
.gnu.sgstubs : ALIGN (32)
{
    . = ALIGN(32);
    _start_sg = ..;
    *(.gnu.sgstubs*)
    . = ALIGN(32);
    _end_sg = .;
} > FLASH-REGION-MED-NSC-AKTIVERET

Kald af sikre funktioner fra ikke-sikker firmware (NS->S)

Normalt vil den sikre firmware blive udviklet og flashet uafhængigt af det ikke-sikre firmware-image. For at bruge nogen af funktionerne i den sikre firmware skal en header-fil (.h), der beskriver de funktioner, der kan kaldes på den sikre side, inkluderes i de ikke-sikre kilder. Derudover skal den ikke-sikre firmware vide, hvor stubbene til finérindgangspunkterne tidligere er blevet placeret i NSC-delene af den sikre hukommelse. Det gøres ved at linke en særlig objektfil (f.eks. CMSE_importLib.o) til den ikke-sikre firmware, som indeholder de nødvendige adresseoplysninger. Denne objektfil skal være blevet genereret under linkningsprocessen af den sikre firmware ved hjælp af følgende cmse linker-indstillinger:

// GCC LINKER-flag, der bruges under SECURE side-linking
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

Den valgfrie "-in-implib=CMSE_importLib.o" kan bruges til at holde poster, der allerede er defineret i ældre versioner af objektfilen, på nøjagtig samme hukommelsesplacering også i den nye version. Dette muliggør tilføjelser til det sikre image, uden at det er nødvendigt også at opdatere det ikke-sikre image (som måske er blevet linket mod en ældre version af import lib før).

Ved at bruge .h-filen og linke mod CMSE_importLib.o-objektet kan den ikke-sikre firmware nu påkalde ControlCriticalIO()-funktionen fra eksemplet ovenfor.

// GCC LINKER-flag brugt under ikke-sikker side-linking
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

Kald af ikke-sikre (callback) funktioner fra sikker firmware (S->NS)

Denne artikel viste, hvordan ARM TrustZone kan bruges til at skabe to isolerede firmwaredele, der er forbundet med veldefinerede små gateway-/finérfunktioner. Disse gør det muligt at kalde funktioner i den sikre firmware fra den ikke-sikre firmware.

Den anden retning, dvs. at den sikre firmware kalder en ikke-sikker funktion, fungerer uden videre, hvis den ikke-sikre funktionsadresse på en eller anden måde gives til sikker kode. Normalt gøres dette ved at definere sikre gateway-funktioner med en ikke-sikker callback-pointer som parameter:

// en c-fil fra secure firmware-projektet, der definerer veneer-gateway-funktioner
// skal kompileres med -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; // gem flygtig pointer fra ikke-sikker kode
 
 // tjek om en given pointer til ikke-sikker hukommelse faktisk er ikke-sikker som forventet
 cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE);
  
 if (cb != 0) {
    /* gør nogle kritiske ting, f.eks. brug andre sikre funktioner */
    cb(); // påkald ikke-sikker call back-funktion
 }else {
   // gør intet, hvis pointeren er forkert
 }
}

Attributten "nonsecure_call" er nødvendig for at instruere den sikre compiler i at rydde de ikke-bankede general purpose-registre, før der hoppes tilbage i den usikre kode, da det ville være en potentiel sikkerhedsrisiko ikke at gøre det. Derudover instruerer den compileren i at rydde LSB af funktionspegerens adresse i den underliggende branch assembler-instruktion. På den måde vil CPU'en gå fra sikker til ikke-sikker tilstand inden for forgreningen.

Da det givne ikke-sikre callback skal behandles som en flygtig variabel, hvis indhold kan ændres af ikke-sikre afbrydelser, er der brug for funktionspointer-kopien "cb". Før callbacken påkaldes, skal dens pointer kontrolleres for kun at pege på ikke-sikker hukommelse. Hvis den peger på sikker hukommelse, ville det også være en sikkerhedsrisiko.

Cortex-M33 kerneregister, (c) ARM®

Tips og yderligere læsning

Husk, at linkerscriptet til den sikre side skal tage højde for sektionen til finérfunktionerne, og linkerscriptet til den ikke-sikre side skal afspejle de hukommelsespartitioner, der er lavet i den sikre firmware. Hvis man inkluderer ikke-sikre kaldbare funktioner i en arkivfil (*.a), skal den linkes med "whole-archive"-linkeroptionen til den sikre firmware. Kun på denne måde vil CMSE import lib indeholde de nødvendige finérfunktioner.

Et par vanskeligheder ved at skrive ARMv8-M Secure-kode - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
Trustzone med ARMv8-m og NXP lpc55s69-evk - Erich Styger
CM33 freeRTOS Eksempel

skrevet af Theodor Rohde (CEO for Lobaro GmbH) den 4. maj 2019.

4 svar
  1. Daniel Oliveira siger:

    Hej, Theodor,

    Tak for dette indlæg og den detaljerede forklaring på Armv8-M-arkitekturen med sikkerhedsudvidelser. Har du i øvrigt nogle gcc-baserede projekter på github, der er relateret til dette emne?

    Vær hilset,
    Daniel

  2. Tobias siger:

    Hej, Daniel,
    Vi planlægger at udgive et minimalt eksempel til nRF9160 ved hjælp af GCC CMSE-udvidelser og FreeRTOS. Dette er planlagt til at være et alternativt miljø for SiP. Jeg tror, det vil tage ca. 1 måned fra nu.

    Hilsen
    Theo

  3. Neculai Agavriloaie siger:

    Hej, Theodor,

    Efter ca. 20 års projektering med MSP430 ønskede jeg at foretage en ændring.
    I mit nye projekt vil jeg bruge Trustzone-teknologi, og jeg havde virkelig brug for at forstå, hvordan den fungerer.

    Denne side er det eneste sted, hvor betjeningen er meget enkel og tydeligt beskrevet.

    Vær hilset,
    Neculai

Kommentarer er lukkede.