Koncepten bakom ARMv8-M TrustZone

Använda GCC CMSE (Cortex-M Security Extensions) med ARM Cortex-M33- och M23-kärnor

Introduktion och motivation

Denna artikel är resultatet av mina första steg med den nya nRF9160 System-i-paket från Nordic Semiconductor. Denna SiP kan användas för att skapa IoT-sensornoder med cellulär anslutning till internet. Den innehåller ett modem för de nya "IoT-optimerade" lågeffektvarianterna av LTE-standarden, nämligen NB.IoT och LTE-M, som är avsedda att ersätta traditionell M2M-anslutning via 2G-nätverk.

Konceptuellt är SiP mycket intressant för oss på Lobaro eftersom den bryter med det utbredda konceptet att ha en applikationsmikroprocessor som talar över en seriell linje med hjälp av AT-kommandon med ett separat mobilmodem. Dessutom använder den den senaste ARM Cortex-M33 CPU-kärnan med nya säkerhetsfunktioner som är särskilt inriktade på säkra IoT-applikationer. LTE-modemet är direkt integrerat i SiP och kommunicerar med applikationsprocessorn med hjälp av interprocessorkommunikation baserad på delat minne. Den täta kopplingen möjliggör bättre optimering för låg effekt och mindre PCB-avtryck, förutom att det blir enklare att säkra IoT-noden.

Cortex-M33-processorn i nRF9160 använder den nya ARMv8-M-arkitekturen som erbjuder en ny funktion som kallas "ARM Förtroendezon". Följande artikel återspeglar min tolkning av de underliggande begreppen och deras praktiska tillämpning med GNU ARM GCC-kompilatorn och dess CMSE-funktioner (=Cortex M Security Extensions). Eftersom det för närvarande (maj 2019) inte finns mycket information att hitta online hoppas jag att den här artikeln kan hjälpa dig att få en snabbare start i ämnet än vad jag hade.

Även om jag använder nRF9160 Cortex-M33 SiP är de flesta koncept som beskrivs i det följande giltiga även för andra ARMv8-M-aktiverade Cortex-M33- och Cortex-M23-enheter från olika kiseltillverkare. Detsamma gäller för användning av andra ARM-kompilatorer än GCC.

Cortex-M33-kärna, (c) ARM

Säkra och icke säkra CPU-driftlägen

Utöver de klassiska CPU-driftlägena Thread (normal kod) och Handler (IRQ/Exception-kod) har ARMv8-M-arkitekturen infört säkra och icke-säkra attribut. Efter återställning startar CPU:n alltid i säkert läge och kan komma åt alla minnesregioner och använda alla periferienheter utan begränsningar. Detta är det normala driftläget för äldre arkitekturer som Cortex-M4 eller Cortex-M3. Din ARMv8-M-baserade CPU kan förbli i detta läge utan att använda TrustZone-funktionerna, men då blir det svårare att skapa säkra applikationer. Du måste dubbelkolla varje del av din kod, för om det finns en bugg kan en (fjärr)angripare få tillgång till hela ditt system och/eller lagrade autentiseringsuppgifter.

Tanken med TrustZone är att dölja säkerhetsrelevant kod, data och kringutrustning i ett litet område på enheten. Denna region kallas "säker" medan övriga delar av enheten behandlas som (potentiellt) "icke-säker". Uppdelningen av minnesregioner, avbrott och kringutrustning i säkra och icke-säkra är upp till dig. Normalt kommer dina första kodrader efter påslagning att utföra dessa beslut baserat på dina applikationsbehov. I "Säker partitionshanterare" kodexempel för nRF9160 Nordic valde att använda en flashminnespartition på 256kB secure och 768kB non-secure.

CPU-kärnan har två bankade versioner (säker/icke säker) av register som är relevanta för körningen, t.ex. stackpekarna och systemkontrollblocket (SCB). CPU:ns aktuella säkerhetsläge definierar vilken registervariant som skall användas. Bankmekanismen finns på samma sätt för vissa periferienheter (t.ex. SysTick) som implementeras två gånger i hårdvaran. Det säkra programmet kan alltid komma åt båda versionerna. Efter att den icke-säkra stackpekaren och platsen för den icke-säkra vektoradressen har konfigurerats använder den säkra startkoden en funktionspekare till vektortabellen för den icke-säkra firmware. Från denna punkt kör processorn den icke-säkra firmware.

Den icke-säkra fasta programvaran får inte vara medveten om att den säkra fasta programvaran finns. Den kan vara "normalt" utvecklad och distribuerad som på äldre kärnor med sitt linker-script som endast pekar på de definierade icke-säkra minnesregionerna. Efter att ha hoppat in i den icke-säkra fasta programvaran körs processorn i icke-säkert läge och kan inte längre direkt komma åt någon säker kod, data eller kringutrustning utan att utlösa ett säkerhetsundantag. Det enda giltiga sättet att komma åt säkra funktioner från icke-säker kod är att anropa speciella gateway- / entry- / veneer-funktioner som den säkra sidan kan tillhandahålla. Resten av den här artikeln visar hur detta fungerar i detalj med hjälp av ARM GCC och dess implementering av CMSE (Cortex M Security-Extensions).

Icke-säkert anropbara (NSC) minnesregioner och ASM-instruktionen för säker gateway (SG)

För att tillåta icke-säkra -> säkra funktionsanrop måste en viss (liten) del av den säkra flashregionen konfigureras som "Non-secure callable" (NSC) i den säkra firmware-koden. På nRF9160 görs detta med hjälp av periferienheten SPU.

Som beskrivits tidigare kan icke-säker kod inte anropa funktioner i den säkra minnesdomänen utan att ett undantag uppstår i ARMv8-M-kärnan. Genom att markera en liten del av det säkra minnet som NSC sänks denna begränsning på så sätt att anrop/förgreningar till platser som innehåller monteringsinstruktionen "SG" (Secure Gateway) som första opkod nu är tillåtna. Från punkten efter SG-instruktionen kommer programmet att ta en annan förgrening till implementeringen av säkra funktioner som ligger i det mer begränsade säkra minnet, t.ex. inte markerat som NSC. Att anropa en säker funktion från icke-säker kod som denna är en tvåstegsprocess med kedjade hopp först till NSC-märkt minne (med SG-instruktion) och sedan vidare med ett hopp till den faktiska funktionskroppen i säkert minne. Genom att använda denna process separeras ingångarna till det säkra minnet från resten av det säkra minnet. Anledningen till att inte använda SG-instruktionen direkt i början av funktioner i det säkra minnet och undvika tvåstegsprocessen förklaras av ARM:s tekniska support här: Skäl till att införa NSC-regionerna.

Definiera icke-säkra anropbara funktioner i säker firmware

Som tur är döljer ARMv8-M-kompilatorer den tidigare beskrivna tvåstegsprocessen för programmeraren när en säker funktion anropas från den icke-säkra fasta programvaran. En säker funktion som utformats som ingångspunkt för icke-säker fast programvara måste markeras med attributet för icke-säker ingång efter att den deklarerats i den säkra fasta programvaran:

// någon c-fil från projektet för säker firmware som definierar funktioner för faner-gateway
// måste kompileras med gcc-flaggan -mcmse (!)
#include "arm_cmse.h"
__attribut__((cmse_nonsecure_entry)) void ControlCriticalIO(){
// gör några kritiska saker
}

Detta instruerar kompilatorn att automatiskt generera två koddelar för den ovan beskrivna processen om kompilering sker med "-mcmse" ARM gcc-flagga.

// GCC COMPILER-flaggor som används under uppbyggnad av säker sida
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

Den faktiska funktionskroppen kommer att placeras av länkaren i den säkra .text-sektionen som vanligt, men delen med SG- och greninstruktioner kommer att placeras i en speciell sektion som heter ".gnu.sgstubs" eller liknande för kompilatorer som inte är GNU C-kompilatorer.

De små redirect-funktionerna i NSC-regionen kallas också "veneer"-funktioner eller "SG stups", eftersom de är så små och bara gör en SG-instruktion och en förgrening. Länkfilen i den säkra firmware måste placera all veneer-kod i den säkra minnesregionen som den säkra firmware kommer att deklarera som NSC som en del av sin säkra startprocess innan den hoppar in i den icke-säkra firmware.

// Linkerscript-avsnitt för TrustZone Secure Gateway-faner
.gnu.sgstubs : ALIGN (32)
{
    . = ALIGN(32);
    _start_sg = ..;
    *(.gnu.sgstubs*)
    . = ALIGN(32);
    _end_sg = .;
} > FLASH-REGION-MED-NSC-AKTIVERAD

Anropa säkra funktioner från icke-säker firmware (NS->S)

Normalt kommer den säkra fasta programvaran att utvecklas och flashas oberoende av den icke-säkra fasta programvaran. För att kunna använda någon av funktionerna i den säkra firmware måste en headerfil (.h) som beskriver de anropbara funktionerna på den säkra sidan inkluderas i de icke-säkra källorna. Utöver detta måste den icke-säkra firmware veta var stubbarna för fanerets ingångspunkter har placerats tidigare i NSC-delarna av det säkra minnet. Detta görs genom att en speciell objektfil (t.ex. CMSE_importLib.o) länkas till den icke-säkra firmware som innehåller den adressinformation som behövs. Denna objektfil måste ha genererats under länkningsprocessen för secure firmware med hjälp av följande alternativ för cmse linker:

// GCC LINKER-flaggor som används vid säker sidolänkning
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

Tillvalet "-in-implib=CMSE_importLib.o" kan användas för att behålla poster som redan definierats i äldre versioner av objektfilen på exakt samma minnesplats även i den nya versionen. Detta möjliggör tillägg till den säkra avbildningen utan att behöva uppdatera den icke-säkra avbildningen också (som kan ha länkats mot en äldre version av import lib tidigare).

Med hjälp av .h-filen och länkning mot CMSE_importLib.o-objektet kan den icke-säkra firmware nu anropa funktionen ControlCriticalIO() från exemplet ovan.

// GCC LINKER-flaggor som används under icke-säker sidolänkning
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

Anropa icke-säkra funktioner (callback) från säker firmware (S->NS)

Den här artikeln visar hur ARM TrustZone kan användas för att skapa två isolerade delar av den inbyggda programvaran som är förbundna med väldefinierade små gateway-/fanerfunktioner. Dessa gör att funktioner i den säkra firmware kan anropas från den icke-säkra firmware.

Den andra riktningen, dvs. att säker firmware anropar en icke-säker funktion, fungerar direkt om adressen till den icke-säkra funktionen på något sätt ges till säker kod. Normalt görs detta genom att definiera säkra gateway-funktioner med en icke-säker callback-pekare som parameter:

// någon c-fil från projektet för säker firmware som definierar funktioner för faner-gateway
// måste kompileras med gcc-flaggan -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; // spara flyktig pekare från icke-säker kod
 
 // kontrollera om given pekare till icke-säkert minne faktiskt är icke-säker som förväntat
 cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE);
  
 if (cb != 0) {
    /* Gör några kritiska saker, t.ex. använd andra säkra funktioner */
    cb(); // anropa icke-säker återkallningsfunktion
 }else {
   // gör ingenting om pekaren är felaktig
 }
}

Attributet "nonsecure_call" behövs för att instruera den säkra kompilatorn att rensa de icke bankade general purpose-registren innan man hoppar tillbaka till den icke säkra koden eftersom det skulle innebära en potentiell säkerhetsrisk att inte göra det. Dessutom instrueras kompilatorn att rensa LSB i funktionspekarens adress i den underliggande branch assembler-instruktionen. Genom att göra detta kommer processorn att övergå från säkert till icke-säkert tillstånd inom förgreningen.

Eftersom den givna icke-säkra callbacken måste behandlas som en flyktig variabel, vars innehåll kan ändras av icke-säkra avbrott, behövs funktionspekarkopian "cb". Innan callbacken anropas måste dess pekare kontrolleras så att den endast pekar på icke-säkert minne. Om den skulle peka på ett säkert minne skulle det också vara en säkerhetsrisk.

Cortex-M33-kärnregister, (c) ARM® -kärnregister

Tips och vidare läsning

Tänk på att länkskriptet för den säkra sidan måste ta hänsyn till sektionen för veneer-funktionerna och länkskriptet för den icke-säkra sidan måste återspegla de minnespartitioner som gjorts i den säkra fasta programvaran. Om icke-säkra anropbara funktioner inkluderas i en arkivfil (*.a) måste den länkas med alternativet "whole-archive" till den säkra firmware. Endast på detta sätt kommer CMSE import lib att innehålla de fanerfunktioner som behövs.

Några svårigheter med att skriva säker ARMv8-M-kod - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
Trustzone med ARMv8-m och NXP lpc55s69-evk - Erich Styger
CM33 freeRTOS Exempel

skriven av Theodor Rohde (VD för Lobaro GmbH) den 4 maj 2019.

4 Kommentarer
  1. Daniel Oliveira säger:

    Hej Theodor,

    Tack för det här inlägget och den detaljerade förklaringen om Armv8-M-arkitektur med säkerhetsförlängningar. Förresten, har du några gcc-baserade projekt på github som är relaterade till detta ämne?

    Hälsningar,
    Daniel

  2. Tobias säger:

    Hej Daniel,
    Vi planerar att släppa ett minimalt exempel för nRF9160 med hjälp av GCC CMSE-tillägg och FreeRTOS. Detta är planerat att vara en alternativ miljö för SiP. Jag tror att detta kommer att ta ca. 1 månad från nu.

    Hälsningar
    Theo

  3. Neculai Agavriloaie säger:

    Hej Theodor,

    Efter ca 20 år av projektering med MSP430 ville jag göra en förändring.
    I mitt nya projekt vill jag använda Trustzone-tekniken och jag behövde verkligen förstå hur den fungerar.

    Denna sida är den enda plats där arbetssättet är mycket enkelt och tydligt beskrivet.

    Hälsningar,
    Neculai

Kommentering är stängd.