Konseptene bak ARMv8-M TrustZone
Bruke GCC CMSE (Cortex-M Security Extensions) med ARM Cortex-M33- og M23-kjerner
Introduksjon og motivasjon
Denne artikkelen er et resultat av mine første skritt med det nye nRF9160 System-i-pakke fra Nordic Semiconductor. Denne SiP-enheten kan brukes til å lage IoT-sensornoder med mobiltilkobling til internett. Den integrerer et modem for de nye "IoT-optimaliserte" laveffektsvariantene av LTE-standarden, nemlig NB.IoT og LTE-M, som skal erstatte tradisjonell M2M-tilkobling via 2G-nettverk.
Konseptuelt er SiP svært interessant for oss i Lobaro fordi den bryter med det utbredte konseptet med en applikasjonsmikroprosessor som snakker over seriell linje ved hjelp av AT-kommandoer med et separat mobilmodem. I tillegg bruker den den nyeste ARM Cortex-M33 CPU-kjernen med nye sikkerhetsfunksjoner rettet mot spesielt sikre IoT-applikasjoner. LTE-modemet er direkte integrert i SiP-enheten og kommuniserer med applikasjonsprosessoren ved hjelp av interprosessor-kommunikasjon basert på delt minne. Denne tette koblingen gjør det mulig å optimere strømforbruket og ta mindre plass på kretskortet, i tillegg til at det blir enklere å sikre IoT-noden.
Cortex-M33-prosessoren i nRF9160 bruker den nye ARMv8-M-arkitekturen som tilbyr en ny funksjon kalt "ARM TrustZone". Følgende artikkel gjenspeiler min tolkning av de underliggende konseptene og deres praktiske anvendelse ved hjelp av GNU ARM GCC-kompilatoren og dens CMSE-funksjoner (=Cortex M Security Extensions). Siden det for øyeblikket (mai 2019) ikke er mye informasjon å finne på nettet, håper jeg at denne artikkelen kan hjelpe deg med å komme raskere i gang med emnet enn jeg gjorde.
Selv om jeg bruker nRF9160 Cortex-M33 SiP, er de fleste konseptene i det følgende også gyldige for andre ARMv8-M-aktiverte Cortex-M33- og Cortex-M23-enheter fra andre silisiumleverandører. Det samme gjelder for bruk av andre ARM-kompilatorer enn GCC.
Cortex-M33-kjerne, (c) ARM®.
Sikre og ikke-sikre CPU-driftstilstander
I tillegg til de klassiske CPU-driftstilstandene Thread (normal kode) og Handler (IRQ/Exception-kode) introduserer ARMv8-M-arkitekturen sikre og ikke-sikre attributter. Etter tilbakestilling starter CPU-en alltid i sikker modus og har tilgang til alle minneområder og kan bruke alle eksterne enheter uten begrensninger. Dette tilsvarer normal driftsmodus for eldre arkitekturer som Cortex-M4 eller Cortex-M3. Din ARMv8-M-baserte CPU kan forbli i denne modusen uten å bruke TrustZone-funksjonene, men da blir det vanskeligere å lage sikre applikasjoner. Du må dobbeltsjekke hver eneste del av koden din, for hvis det finnes en feil, kan en (ekstern) angriper få tilgang til hele systemet og/eller lagret legitimasjon.
Ideen med TrustZone er å skjule sikkerhetsrelevant kode, data og eksterne enheter i et lite område av enheten. Dette området kalles "sikkert", mens resten av enheten behandles som (potensielt) "ikke-sikkert". Inndelingen av minneområder, avbrudd og periferiutstyr i sikre og usikre områder er opp til deg. Normalt vil de første kodelinjene etter at enheten er slått på, ta disse beslutningene basert på applikasjonens behov. I "Secure Partition Manager"kodeeksempel for nRF9160 Nordic bestemte seg for å bruke en flashminnepartisjon på 256 kB sikker og 768 kB ikke-sikker.
CPU-kjernen opprettholder to bankede versjoner (sikker/ikke-sikker) av kjøringsrelevante registre, f.eks. stakkpekere og systemkontrollblokken (SCB). Den aktuelle CPU-sikkerhetsmodusen definerer hvilken registervariant som skal brukes. Bankmekanismen finnes også for enkelte periferienheter (f.eks. SysTick) som er implementert to ganger i maskinvaren. Det sikre programmet har alltid tilgang til begge versjonene. Etter å ha satt opp den ikke-sikre stakkpekeren og plasseringen av den ikke-sikre vektoradressen, bruker den sikre oppstartskoden en funksjonspeker til vektortabellen til den ikke-sikre fastvaren. Fra dette punktet kjører CPU-en den ikke-sikre fastvaren.
Den ikke-sikre fastvaren må ikke være klar over eksistensen av den sikre fastvaren. Den kan utvikles og distribueres "normalt" på samme måte som på eldre kjerner med linker-script som bare peker på de definerte ikke-sikre minneregionene. Etter å ha hoppet inn i den ikke-sikre fastvaren kjører CPU-en i ikke-sikker modus og kan ikke lenger få direkte tilgang til sikker kode, data eller periferiutstyr uten å utløse et sikkerhetsunntak. Den eneste gyldige måten som gjenstår for å få tilgang til sikre funksjoner fra ikke-sikker kode, er å påkalle spesielle gateway-/inngangs-/finérfunksjoner som den sikre siden kan tilby. Resten av denne artikkelen viser hvordan dette fungerer i detalj ved hjelp av ARM GCC og implementeringen av CMSE (Cortex M Security-Extensions).
Ikke-sikre minneområder som kan kalles (NSC) og ASM-instruksjonen for sikker gateway (SG)
For å tillate ikke-sikre -> sikre funksjonskall må en (liten) del av den sikre flash-regionen konfigureres som "Non-secure callable" (NSC) i den sikre firmware-koden. På nRF9160 gjøres dette ved hjelp av SPU-periferien.
Som beskrevet tidligere kan ikke-sikker kode ikke kalle funksjoner i det sikre minnedomenet uten at det oppstår et unntak i ARMv8-M-kjernen. Ved å merke en liten del av det sikre minnet som NSC er denne begrensningen redusert slik at det nå er tillatt å kalle/forgrene seg til steder som inneholder assemblyinstruksjonen "SG" (Secure Gateway) som første opcode. Fra punktet etter SG-instruksjonen vil programmet ta en ny forgrening til implementeringen av sikre funksjoner som ligger i det mer begrensede sikre minnet, dvs. ikke merket som NSC. Å kalle en sikker funksjon fra ikke-sikker kode på denne måten er en totrinnsprosess med kjedede hopp først til NSC-merket minne (med SG-instruksjon) og deretter videre til selve funksjonskroppen i sikkert minne. Ved å bruke denne prosessen skilles oppføringene til det sikre minnet fra resten av det sikre minnet. Årsaken til at man ikke bør bruke SG-instruksjonen direkte i begynnelsen av funksjoner i det sikre minnet og unngå denne totrinnsprosessen, er forklart av ARMs tekniske support her: Begrunnelse for å innføre NSC-regionene.
Definere ikke-sikre funksjoner som kan kalles i sikker fastvare
Heldigvis skjuler ARMv8-M-kompilatorene den tidligere beskrevne totrinnsprosessen for programmereren når en sikker funksjon skal kalles fra en ikke-sikker fastvare. En sikker funksjon som er utformet som inngangspunkt for ikke-sikker fastvare, må merkes med attributtet nonsecure entry etter at den er erklært i den sikre fastvaren:
// en eller annen c-fil i secure firmware-prosjektet som definerer funksjoner for veneer-gatewayen // må kompileres med gcc-flagget -mcmse (!) #include "arm_cmse.h" __attribute__((cmse_nonsecure_entry)) void ControlCriticalIO(){ // gjør noen kritiske ting }
Dette instruerer kompilatoren til automatisk å generere to kodedeler for prosessen som er beskrevet ovenfor, hvis du kompilerer med "-mcmse"ARM gcc-flagg.
// GCC COMPILER-flagg som brukes under bygging av sikre sider arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]
Selve funksjonskroppen blir plassert av linker i den sikre .text-seksjonen som vanlig, men delen med SG- og branch-instruksjonene blir plassert i en spesiell seksjon kalt ".gnu.sgstubs"eller lignende for kompilatorer som ikke er GNU C-kompilatorer.
De små viderekoblingsfunksjonene i NSC-regionen kalles også "veneer"-funksjoner eller "SG nudges", fordi de er så små og bare utfører én SG-instruksjon og en forgrening. Linkerfilen til den sikre fastvaren må plassere all finerkode i den sikre minneregionen som den sikre fastvaren vil erklære som NSC som en del av den sikre oppstartsprosessen før den hopper inn i den ikke-sikre fastvaren.
// Linkerscript-seksjon for TrustZone Secure Gateway-finerer .gnu.sgstubs : ALIGN (32) { . = ALIGN(32); _start_sg = .; *(.gnu.sgstubs*) . = ALIGN(32); _end_sg = .; } > FLASH-REGION-MED-NSC-AKTIVERT
Kaller sikre funksjoner fra ikke-sikker fastvare (NS->S)
Normalt vil den sikre fastvaren utvikles og flashes uavhengig av den ikke-sikre fastvaren. For å kunne bruke funksjonaliteten til den sikre fastvaren må en headerfil (.h) som beskriver de sikre sidefunksjonene som kan kalles, inkluderes i de ikke-sikre kildene. I tillegg til dette må den ikke-sikre fastvaren vite hvor inngangspunktstubbene i finéren er plassert i NSC-delene av det sikre minnet. Dette gjøres ved å koble en spesiell objektfil (f.eks. CMSE_importLib.o) til den ikke-sikre fastvaren som inneholder den nødvendige adresseinformasjonen. Denne objektfilen må ha blitt generert under koblingsprosessen for sikker fastvare ved hjelp av følgende cmse linker-alternativer:
// GCC LINKER-flagg som brukes under SECURE-sidelinking 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 brukes til å beholde oppføringer som allerede er definert i eldre versjoner av objektfilen på nøyaktig samme minneplass også i den nye versjonen. Dette gjør det mulig å legge til nye elementer i det sikre objektet uten å måtte oppdatere det ikke-sikre objektet (som kan ha blitt lenket mot en eldre versjon av importlibben tidligere).
Ved hjelp av .h-filen og kobling mot CMSE_importLib.o-objektet kan den ikke-sikre fastvaren nå påkalle ControlCriticalIO()-funksjonen fra eksemplet ovenfor.
// GCC LINKER-flagg som brukes under ikke-sikker sidekobling arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]
Kaller ikke-sikre funksjoner (callback) fra sikker fastvare (S->NS)
Denne artikkelen demonstrerte hvordan ARM TrustZone kan brukes til å opprette to isolerte fastvaredeler som er forbundet med veldefinerte små gateway-/finérfunksjoner. Disse gjør det mulig å kalle funksjoner i den sikre fastvaren fra den ikke-sikre fastvaren.
Den andre retningen, dvs. at sikker fastvare kaller en ikke-sikker funksjon, fungerer uten videre hvis adressen til den ikke-sikre funksjonen på en eller annen måte gis til sikker kode. Normalt gjøres dette ved å definere sikre gateway-funksjoner med en ikke-sikker callback-peker som parameter:
// en eller annen c-fil i secure firmware-prosjektet som definerer funksjoner for veneer-gatewayen // må kompileres med gcc-flagget -mcmse (!) #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; // lagre flyktig peker fra ikke-sikker kode // sjekk om den gitte pekeren til ikke-sikkert minne faktisk er ikke-sikker som forventet cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE); if (cb != 0) { /* gjør noen kritiske ting, f.eks. bruk andre sikre funksjoner */ cb(); // påkaller ikke-sikker tilbakeringingsfunksjon }else { // gjør ingenting hvis pekeren er feil } }
Attributtet "nonsecure_call" er nødvendig for å instruere den sikre kompilatoren om å tømme de ikke-bankede general purpose-registrene før den hopper tilbake til den ikke-sikre koden, siden det ville utgjøre en potensiell sikkerhetsrisiko hvis dette ikke ble gjort. I tillegg instrueres kompilatoren til å tømme LSB i funksjonspekerens adresse i den underliggende forgreningsassemblerinstruksjonen. På denne måten vil CPU-en gå fra sikker til ikke-sikker tilstand i løpet av forgreningen.
Fordi den gitte ikke-sikre tilbakekallingen må behandles som en flyktig variabel, hvis innhold kan endres av ikke-sikre avbrudd, er det nødvendig å kopiere funksjonspekeren "cb". Før callback-funksjonen påkalles, må det kontrolleres at pekeren kun peker til et ikke-sikkert minne. Hvis den peker til et sikkert minne, vil det også utgjøre en sikkerhetsrisiko.
Cortex-M33-kjerneregisteret, (c) ARM®.
Tips og videre lesning
Husk at linkerscriptet for den sikre siden må ta hensyn til seksjonen for finérfunksjonene, og at linkerscriptet for den ikke-sikre siden må gjenspeile minnepartisjonene som er laget i den sikre fastvaren. Hvis ikke-sikre funksjoner som kan kalles, er inkludert i en arkivfil (*.a), må den lenkes til den sikre fastvaren med alternativet "whole-archive". Bare på denne måten vil CMSE-importbiblioteket inkludere de nødvendige finérfunksjonene.
Noen komplikasjoner ved skriving av 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 av Theodor Rohde (CEO i Lobaro GmbH) 4. mai 2019.
Hei, Theodor,
Takk for dette innlegget og den detaljerte forklaringen på Armv8-M-arkitekturen med sikkerhetsutvidelser. Har du forresten noen gcc-baserte prosjekter på github relatert til dette emnet?
Vær hilset,
Daniel
Hei, Daniel,
planlegger vi å lansere et minimalt eksempel for nRF9160 ved hjelp av GCC CMSE-utvidelser og FreeRTOS. Dette er planlagt som et alternativt miljø for SiP. Jeg tror dette vil ta ca. 1 måned fra nå.
Hilsener
Theo
Hei, Theodor,
Etter å ha projisert med MSP430 i omtrent 20 år ønsket jeg å gjøre en endring.
I mitt nye prosjekt vil jeg bruke Trustzone-teknologi, og jeg trengte virkelig å forstå hvordan den fungerer.
Denne siden er det eneste stedet der virkemåten er svært enkel og tydelig beskrevet.
Vær hilset,
Neculai
Takk skal du ha :-)