Concepten achter de ARMv8-M TrustZone

GCC CMSE (Cortex-M Security Extensions) gebruiken met ARM Cortex-M33 en M23-kernen.

Inleiding & 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 creëren. Hij integreert een modem voor de nieuwe "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 zeer interessant voor ons bij Lobaro omdat het breekt met het wijdverbreide concept van het hebben van een applicatie microprocessor die praat over 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 gericht op speciaal beveiligde IoT-toepassingen. De LTE-modem is rechtstreeks 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 betere 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 genaamd "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 momenteel (mei 2019) niet veel informatie online te vinden is, hoop ik dat dit artikel u 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 siliciumverkopers. Hetzelfde geldt voor het gebruik van andere ARM-compilers dan GCC.

Cortex-M33 kern, (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 veilige modus en heeft hij toegang tot alle geheugengebieden en kan hij alle randapparatuur zonder beperkingen gebruiken. Dit lijkt op de normale bedrijfsmodus van oudere architecturen zoals de Cortex-M4 of Cortex-M3. Uw ARMv8-M gebaseerde CPU kan in deze modus blijven zonder gebruik te maken van de TrustZone functies, maar dan wordt het maken van veilige toepassingen moeilijker. U moet elk stuk van uw code dubbel controleren, want als er een bug is, kan een (externe) aanvaller toegang krijgen tot uw volledige systeem en/of opgeslagen referenties.

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

De CPU-kern onderhoudt twee bankversies (veilig/onveilig) van voor de uitvoering relevante registers, bijvoorbeeld de stackpointers en het systeembesturingsblok (SCB). De huidige beveiligingsmodus van de CPU bepaalt welke registervariant wordt gebruikt. Het bankmechanisme is ook aanwezig voor sommige randapparatuur (bijv. SysTick) die tweemaal in hardware is geïmplementeerd. 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. Hij kan "normaal" worden ontwikkeld en ingezet zoals op oudere cores, waarbij het linker-script alleen naar de niet-veilige geheugengebieden wijst. Na het springen naar de niet-veilige firmware draait de CPU in niet-veilige modus en heeft geen directe toegang meer tot beveiligde code, gegevens of randapparatuur zonder een beveiligingsuitzondering te veroorzaken. 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 demonstreert in detail hoe dit werkt met behulp van de 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 mogelijk te maken 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 geheugengebied aanroepen zonder een uitzondering te maken in de ARMv8-M kern. Door een klein deel van het beveiligde geheugen als NSC te markeren wordt deze beperking verminderd, zodat aanroepen / vertakkingen naar locaties met de "SG" (Secure Gateway) assembly-instructie als eerste opcode nu zijn toegestaan. Vanaf het punt na de SG-instructie zal het programma een andere tak nemen naar de uitvoering van de beveiligde functies die in het veiliger geheugen liggen, d.w.z. niet gemarkeerd als NSC. Het aanroepen van een beveiligde functie vanuit niet-veilige code als deze is een proces in twee stappen, met aaneengeschakelde sprongen eerst naar het NSC-gemarkeerde geheugen (met de SG-instructie) en vervolgens een sprong naar het eigenlijke functiegedeelte in het beveiligde geheugen. Door dit proces worden de entries naar het beveiligde geheugen gescheiden van de rest van het beveiligde geheugen. De reden om de SG-instructie niet rechtstreeks aan het begin van functies in het veilige geheugen te gebruiken en het tweestappenproces te vermijden, wordt hier toegelicht door de technische ondersteuning van ARM: Redenen voor de invoering van de NSC-regio's.

Definiëren van niet-veilige opvraagbare functies in veilige firmware

Gelukkig verbergen ARMv8-M-compilers het hierboven beschreven proces in twee stappen voor de programmeur wanneer hij een beveiligde functie aanroept vanuit de niet-veilige firmware. Een veilige functie die is ontworpen als ingangspunt voor niet-veilige firmware moet worden gemarkeerd met het kenmerk niet-veilige ingang na de declaratie ervan in de veilige firmware:

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

Dit instrueert de compiler om automatisch twee delen code te genereren voor het hierboven beschreven proces indien 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 functiehuis wordt door de linker in de beveiligde .text-sectie geplaatst zoals gewoonlijk, maar het deel 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 omleidingsfuncties in de NSC-regio worden ook wel "veneer"-functies of "SG-nudges" genoemd, omdat ze zo klein zijn en slechts één SG-instructie en een vertakking uitvoeren. Het linkerbestand van de veilige firmware moet alle veneer-code plaatsen in de veilige geheugenregio die de veilige firmware als NSC zal verklaren als onderdeel van het veilige opstartproces, voordat wordt overgesprongen naar de niet-veilige firmware.

// 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

Aanroepen van beveiligde functies vanuit niet-beveiligde firmware (NS->S)

Normaal gesproken wordt de veilige firmware onafhankelijk van het niet-veilige firmware-image ontwikkeld en geflasht. Om elke functionaliteit van de veilige firmware te kunnen gebruiken, moet een headerbestand (.h) met een beschrijving van de aanroepbare functies van de veilige kant worden opgenomen in de niet-veilige bronnen. Daarnaast moet de niet-veilige firmware weten waar de veneer entry point stubs eerder zijn geplaatst in de NSC delen van het beveiligde geheugen. Dit wordt gedaan door een speciaal objectbestand (bijv. 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-linkopties:

// 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 worden gebruikt om reeds in oudere versies van het objectbestand gedefinieerde entries op exact dezelfde geheugenplaats ook in de nieuwe versie te houden. Dit maakt toevoegingen aan de beveiligde image mogelijk zonder dat ook de niet-beveiligde image hoeft te worden bijgewerkt (die mogelijk eerder is gekoppeld aan een oudere versie van de import lib).

Met behulp van het .h-bestand en koppeling tegen het object CMSE_importLib.o kan de niet-beveiligde firmware nu de functie ControlCriticalIO() uit het bovenstaande voorbeeld aanroepen.

// GCC LINKER-vlaggen gebruikt tijdens niet-veilige koppeling aan de zijkant
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

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

Dit artikel laat zien hoe ARM TrustZone kan worden gebruikt om twee geïsoleerde firmwaredelen te creëren die verbonden zijn door goed gedefinieerde kleine gateway- / veneerfuncties. Hierdoor kunnen functies van de beveiligde firmware worden aangeroepen vanuit de niet-beveiligde firmware.

De andere richting, dat wil zeggen de veilige firmware die een niet-veilige functie aanroept, werkt out of the box, indien het adres van de niet-veilige functie op de een of andere manier aan veilige code wordt gegeven. Normaal wordt dit gedaan door veilige gateway-functies te definiëren met een niet-veilige callback-pointer als parameter:

// een c-bestand van een veilig firmware-project dat de functies van de veneer-gateway 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) {
    /* enkele kritieke dingen doen, bijvoorbeeld andere veilige functies gebruiken */
    cb(); // niet-veilige terugroepfunctie aanroepen
 }else {
   // niets doen als pointer onjuist is
 }
}

Het attribuut "nonsecure_call" is nodig om de veilige compiler opdracht te geven de niet-gebankte registers voor algemene doeleinden vrij te maken voordat wordt teruggesprongen naar de niet-veilige code, aangezien het niet doen daarvan een potentieel veiligheidsrisico zou inhouden. Daarnaast wordt de compiler opgedragen de LSB van het adres van de functiepunten in de onderliggende branch assembler instructie te wissen. Hierdoor gaat de CPU binnen de branch van een veilige naar een niet-veilige toestand.

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-pointerkopie "cb" nodig. Voordat de callback wordt aangeroepen, moet worden gecontroleerd of de pointer ervan alleen wijst naar niet-beveiligd geheugen. Indien deze naar een beveiligd geheugen zou wijzen, zou dat ook een veiligheidsrisico zijn.

Cortex-M33 kernregister, (c) ARM®

Hints 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. Indien niet-veilige oproepbare functies in een archiefbestand (*.a) worden opgenomen, moet dit met de "whole-archive"-linkeroptie aan de beveiligde firmware worden gekoppeld. Alleen zo zal de CMSE import lib de benodigde veneer functies bevatten.

Enkele fijne kneepjes van het schrijven van ARMv8-M veilige code - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
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 deze post en de gedetailleerde uitleg over Armv8-M-architectuur met beveiligingsuitbreidingen. Btw, heb je op gcc gebaseerde projecten op github gerelateerd aan dit onderwerp?

    Gegroet,
    Daniel

  2. Tobias zegt:

    Hallo Daniel,
    wij zijn van plan 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 aanbrengen.
    In mijn nieuwe project wil ik de Trustzone-technologie gebruiken en ik moest echt begrijpen hoe die werkt.

    Deze pagina is de enige plaats waar de werking zeer eenvoudig en duidelijk wordt beschreven.

    Gegroet,
    Neculai

Reacties zijn gesloten.