ARMv8-M TrustZonen taustalla olevat käsitteet

GCC:n CMSE:n (Cortex-M Security Extensions) käyttö ARM Cortex-M33- ja M23-ytimien kanssa

Johdanto ja motivaatio

Tämä artikkeli on tulosta ensimmäisistä askeleistani uudessa nRF9160 System-in-Package -järjestelmäpaketti Nordic Semiconductor. Tämän SiP:n avulla voidaan luoda IoT-anturisolmuja, joilla on soluliittymä internetiin. Siihen on integroitu modeemi LTE-standardin uusia "IoT-optimoituja" pienitehoisia muunnoksia varten, joita ovat NB.IoT ja LTE-M. Näiden muunnosten tarkoituksena on korvata perinteiset M2M-yhteydet 2G-verkoissa.

Konseptuaalisesti SiP on erittäin mielenkiintoinen meille Lobaro:ssä, koska se rikkoo laajalle levinneen käsitteen, jossa yksi sovelluksen mikroprosessori puhuu sarjaportin kautta AT-komentoja käyttäen erillisen matkapuhelinmodeemin kanssa. Lisäksi siinä käytetään uusinta ARM Cortex-M33 -suoritinydintä, jossa on uusia tietoturvaominaisuuksia, jotka on suunnattu erityisesti turvallisille IoT-sovelluksille. LTE-modeemi on integroitu suoraan SiP:hen, ja se kommunikoi sovellusprosessorin kanssa käyttämällä jaettuun muistiin perustuvaa prosessorien välistä viestintää. Tämä tiivis kytkentä mahdollistaa paremman virransäästöoptimoinnin ja pienemmän piirilevyn jalanjäljen, minkä lisäksi IoT-solmun suojaaminen on helpompaa.

nRF9160:n Cortex-M33-prosessori käyttää uutta ARMv8-M-arkkitehtuuria, joka tarjoaa uuden ominaisuuden nimeltä "ARM TrustZone". Seuraava artikkeli kuvastaa tulkintaani taustalla olevista käsitteistä ja niiden käytännön soveltamisesta GNU ARM GCC -kääntäjän ja sen CMSE-ominaisuuksien (=Cortex M Security Extensions) avulla. Koska verkosta ei tällä hetkellä (toukokuu 2019) löydy paljon tietoa, toivon, että tämä artikkeli auttaa sinua pääsemään nopeammin alkuun aiheeseen kuin minä.

Vaikka käytän nRF9160 Cortex-M33 SiP:tä, suurin osa seuraavassa esitetyistä käsitteistä pätee myös muihin ARMv8-M-käyttöjärjestelmällä varustettuihin Cortex-M33- ja Cortex-M23-laitteisiin, jotka ovat peräisin eri piituotteiden valmistajilta. Sama pätee myös muiden ARM-kääntäjien kuin GCC:n käyttöön.

Cortex-M33-ydin, (c) ARM®, (d) ARM®.

Suojattu ja ei-suojattu suorittimen toimintatilat

ARMv8-M-arkkitehtuurissa on perinteisten säikeen (normaali koodi) ja käsittelijän (IRQ- ja poikkeuskoodi) prosessorin toimintatilojen lisäksi turvallisia ja ei-turvallisia ominaisuuksia. Nollauksen jälkeen CPU käynnistyy aina suojatussa tilassa, jolloin se voi käyttää kaikkia muistialueita ja kaikkia oheislaitteita rajoituksetta. Tämä vastaa vanhempien arkkitehtuurien, kuten Cortex-M4:n tai Cortex-M3:n, normaalia toimintatilaa. ARMv8-M-pohjainen suorittimesi voi pysyä tässä tilassa käyttämättä TrustZone-ominaisuuksia, mutta silloin turvallisten sovellusten luominen vaikeutuu. Sinun on tarkistettava jokainen koodisi osa kahdesti, sillä jos siinä on virhe, (etä)hyökkääjä voi päästä käsiksi koko järjestelmään ja/tai tallennettuihin tunnistetietoihin.

TrustZonen ideana on piilottaa tietoturvan kannalta olennainen koodi, tiedot ja oheislaitteet laitteen pienelle alueelle. Tätä aluetta kutsutaan "turvalliseksi", kun taas laitteen muita osia käsitellään (mahdollisesti) "ei-turvallisina". Muistialueiden, keskeytysten ja oheislaitteiden jakaminen turvalliseen ja ei-turvalliseen on sinun päätettävissäsi. Tavallisesti ensimmäiset koodirivit virran kytkemisen jälkeen tekevät nämä päätökset sovelluksesi tarpeiden mukaan. Kohdassa "Secure Partition Manager" nRF9160:n koodiesimerkki Nordic päätti käyttää 256kB:n suojattua ja 768kB:n ei-suojatun flash-muistin osiota.

Suoritinydin ylläpitää kahta pankkiversiota (suojattu / ei-suojattu) suorituksen kannalta merkityksellisistä rekistereistä, kuten pinon osoittimista ja järjestelmän ohjauslohkosta (SCB). CPU:n nykyinen suojaustila määrittää, mitä rekisterivaihtoehtoa käytetään. Pankkimekanismi on vastaavalla tavalla käytössä joissakin oheislaitteissa (esim. SysTick), jotka on toteutettu kahdesti laitteistossa. Suojattu ohjelma voi aina käyttää molempia versioita. Kun ei-turvallinen pino-osoitin ja ei-turvallisen vektoriosoitteen sijainti on määritetty, turvallinen käynnistyskoodi käyttää toiminto-osoitinta ei-turvallisen laiteohjelmiston vektoritaulukkoon. Siitä lähtien CPU suorittaa ei-turvallisen laiteohjelmiston.

Ei-turvallinen laiteohjelma ei saa olla tietoinen suojatun laiteohjelman olemassaolosta. Se voidaan kehittää ja ottaa käyttöön "normaalisti", kuten vanhemmissa ytimissä, ja sen linkkeri-skripti osoittaa vain määriteltyihin ei-turvattuihin muistialueisiin. Kun ei-turvalliseen laiteohjelmaan on hypätty, CPU toimii ei-turvallisessa tilassa, eikä se voi enää käyttää suoraan turvallista koodia, dataa tai oheislaitteita ilman, että se aiheuttaa tietoturvapoikkeuksen. Ainoa pätevä tapa käyttää turvallisia toimintoja ei-turvallisesta koodista käsin on kutsua turvallisen puolen mahdollisesti tarjoamia erityisiä gateway- / entry- / veneer-toimintoja. Tämän artikkelin loppuosassa esitellään yksityiskohtaisesti, miten tämä toimii ARM GCC:n ja sen CMSE-toteutuksen (Cortex M Security-Extensions) avulla.

Ei-suojatusti kutsuttavat (NSC) muistialueet ja suojatun yhdyskäytävän (SG) ASM-käsky.

Jotta ei-turvalliset -> turvalliset toimintokutsut olisivat mahdollisia, jokin (pieni) osa suojatusta flash-alueesta on määriteltävä "Non-secure callable" (NSC) -toiminnoksi suojatussa laiteohjelmakoodissa. nRF9160:ssä tämä tehdään käyttämällä SPU-periferiaa.

Kuten edellä on kuvattu, ei-turvallinen koodi ei voi kutsua turvallisen muistialueen toimintoja ilman, että ARMv8-M-ytimessä syntyy poikkeus. Merkitsemällä pieni osa suojattua muistia NSC:ksi tätä rajoitusta vähennetään siten, että kutsut/haarat paikkoihin, jotka sisältävät ensimmäisenä op-koodina SG-kokoonpanokäskyn (Secure Gateway), ovat nyt sallittuja. SG-käskyn jälkeisestä kohdasta ohjelma tekee uuden haaran suojatun toiminnon toteutukseen, joka sijaitsee rajoitetummassa suojatussa muistissa, jota ei ole merkitty NSC:ksi. Tällaisen suojatun funktion kutsuminen ei-turvallisesta koodista on kaksivaiheinen prosessi, jossa hypätään ensin NSC-merkittyyn muistiin (SG-käskyn avulla) ja sen jälkeen hypätään varsinaisen funktion runkoon suojatussa muistissa. Tätä prosessia käyttämällä suojatun muistin merkinnät erotetaan muusta suojatusta muistista. ARM:n tekninen tuki selittää tässä, miksi SG-käskyä ei käytetä suoraan suojatussa muistissa olevien funktioiden alussa ja miksi kaksivaiheista prosessia vältetään: Syyt NSC-alueiden käyttöönottoon.

Ei-suojattujen kutsuttavien funktioiden määrittäminen suojatussa laiteohjelmistossa

Onneksi ARMv8-M-kääntäjät salaavat ohjelmoijalta edellä kuvatun kaksivaiheisen prosessin, kun turvallista funktiota kutsutaan ei-turvallisesta laiteohjelmistosta. Suojattu funktio, joka on suunniteltu ei-turvallisen laiteohjelmiston sisäänkäynniksi, on merkittävä ei-turvallisen sisäänkäynnin attribuutilla sen jälkeen, kun se on julistettu suojatussa laiteohjelmistossa:

// jokin turvallisen firmware-projektin c-tiedosto, jossa määritellään veneer-gatewayn toiminnot.
// on käännettävä -mcmse gcc-lippulauseella (!).
#include "arm_cmse.h"
__attribute__((cmse_nonsecure_entry)) void ControlCriticalIO(){
// tehdä joitain kriittisiä asioita
}

Tämä ohjeistaa kääntäjää tuottamaan automaattisesti kaksi koodiosaa edellä kuvattua prosessia varten, jos käännetään "-mcmse" ARM gcc-lippu.

// GCC COMPILERin liput, joita käytetään turvallisen puolen rakentamisen aikana.
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

Linkittäjä sijoittaa varsinaisen funktiorungon tavalliseen tapaan turvalliseen .text-osioon, mutta SG- ja haarautumisohjeet sisältävä osa sijoitetaan erityiseen osioon nimeltä ".gnu.sgstubs" tai vastaava muille kuin GNU C-kääntäjille.

NSC-alueen pieniä uudelleenohjausfunktioita kutsutaan myös "viilutoiminnoiksi" tai "SG-stupiksi", koska ne ovat niin pieniä, että ne tekevät vain yhden SG-käskyn ja haarautumisen. Turvallisen laiteohjelmiston linkitystiedoston on sijoitettava kaikki veneer-koodi turvalliseen muistialueeseen, jonka turvallinen laiteohjelmisto ilmoittaa NSC:ksi osana turvallista käynnistysprosessia ennen kuin hyppää ei-turvalliseen laiteohjelmistoon.

// Linkerscript-osio TrustZone Secure Gateway -viiluja varten
.gnu.sgstubs : ALIGN (32)
{
    . = ALIGN(32);
    _start_sg = ..;
    *(.gnu.sgstubs*)
    . = ALIGN(32);
    _end_sg = .;
} > > FLASH-REGION-WITH-NSC-ENABLED

Suojattujen toimintojen kutsuminen ei-suojatusta laiteohjelmistosta (NS->S)

Tavallisesti suojattu laiteohjelmisto kehitetään ja väläytetään ei-turvallisen laiteohjelmiston kuvasta riippumatta. Jos halutaan käyttää turvallisen laiteohjelmiston toimintoja, ei-turvalliseen lähdekoodiin on sisällytettävä otsikkotiedosto (.h), jossa kuvataan kutsuttavat turvallisen puolen toiminnot. Tämän lisäksi ei-turvallisen laiteohjelmiston on tiedettävä, mihin viilun sisäänmenopisteen tyngät on aiemmin sijoitettu suojatun muistin NSC-osissa. Tämä tapahtuu linkittämällä turvattomaan laiteohjelmaan erityinen objektitiedosto (esim. CMSE_importLib.o), joka sisältää tarvittavat osoitetiedot. Tämä objektitiedosto on luotava suojatun laiteohjelmiston linkitysprosessin aikana käyttämällä seuraavia cmse-linkkiohjelman vaihtoehtoja:

// GCC LINKER -liput, joita käytetään SECURE-puolisen linkityksen aikana.
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

Valinnaista "-in-implib=CMSE_importLib.o" voidaan käyttää pitämään vanhoissa versioissa jo määritellyt merkinnät täsmälleen samassa muistipaikassa myös uudessa versiossa. Tämä mahdollistaa lisäykset suojattuun kuvaan ilman, että myös ei-turvattua kuvaa tarvitsee päivittää (joka on ehkä linkitetty aiemmin import lib:n vanhempaa versiota vastaan).

Käyttämällä .h-tiedostoa ja linkittämällä CMSE_importLib.o-objektin kanssa ei-turvallinen laiteohjelmisto voi nyt kutsua ControlCriticalIO()-funktiota yllä olevasta esimerkistä.

// GCC:n LINKER-liput, joita käytetään ei-turvallisen linkityksen aikana.
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

Ei-suojattujen (callback) toimintojen kutsuminen suojatusta laiteohjelmistosta (S->NS)

Tässä artikkelissa esiteltiin, miten ARM TrustZonea voidaan käyttää kahden eristetyn laiteohjelmiston osan luomiseen, jotka on yhdistetty hyvin määritellyillä pienillä yhdyskäytävä-/viilutoiminnoilla. Näiden avulla suojatun laiteohjelmiston toimintoja voidaan kutsua suojaamattomasta laiteohjelmistosta.

Toinen suunta, eli se, että suojattu laiteohjelmisto kutsuu ei-turvallista funktiota, toimii heti, jos ei-turvallisen funktion osoite annetaan jotenkin suojatulle koodille. Tavallisesti tämä tehdään määrittelemällä suojattuja yhdyskäytäväfunktioita, joiden parametrina on ei-turvallinen takaisinsoitto-osoitin:

// jokin turvallisen firmware-projektin c-tiedosto, jossa määritellään viilun yhdyskäytävän toiminnot.
// on käännettävä -mcmse gcc-lippulauseella (!).
#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; // pelastaa haihtuvan osoittimen turvattomalta koodilta.
 
 // Tarkistetaan, onko annettu osoitin ei-turvalliseen muistiin todella ei-turvallinen, kuten odotettiin.
 cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE);
  
 if (cb != 0) { if (cb != 0) {
    /* tee joitakin kriittisiä asioita, esim. käytä muita turvallisia funktioita */
    cb(); // kutsu ei-turvallinen takaisinkutsufunktio.
 }else {
   // älä tee mitään, jos osoitin on väärä
 }
}

Attribuuttia "nonsecure_call" tarvitaan ohjeistamaan turvallista kääntäjää tyhjentämään yleiskäyttöiset rekisterit, joita ei ole varattu pankkiin, ennen kuin hyppää takaisin ei-turvalliseen koodiin, sillä jos näin ei tehdä, se olisi mahdollinen turvallisuusriski. Tämän lisäksi se ohjeistaa kääntäjää tyhjentämään funktion osoittimien osoitteen LSB-osoitteen taustalla olevassa haara-assembleriohjeessa. Näin CPU siirtyy haarautumisen aikana suojatusta tilasta suojaamattomaan tilaan.

Koska annettua ei-turvallista takaisinkutsua on käsiteltävä kuin haihtuvaa muuttujaa, jonka sisältöä voidaan muuttaa ei-turvallisten keskeytysten avulla, tarvitaan funktio-osoitinkopio "cb". Ennen takaisinkutsun kutsumista on tarkistettava, että sen osoitin osoittaa vain turvattomaan muistiin. Jos osoitin osoittaisi turvalliseen muistiin, se olisi myös tietoturvariski.

Cortex-M33-ydinrekisteri, (c) ARM®

Vihjeitä ja lisätietoja

Muista, että suojatun puolen linkkikirjoituksessa on otettava huomioon viilutoimintojen osio ja ei-turvallisen puolen linkkikirjoituksessa on otettava huomioon suojatussa laiteohjelmistossa tehdyt muistin osiot. Jos ei-turvalliset kutsuttavat toiminnot sisällytetään arkistotiedostoon (*.a), se on linkitettävä "whole-archive"-linkkerivaihtoehdolla turvalliseen laiteohjelmaan. Vain tällä tavoin CMSE import lib sisältää tarvittavat viilutustoiminnot.

Muutamia ARMv8-M Secure -koodin kirjoittamisen hienouksia - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
Trustzone ARMv8-m:n ja NXP lpc55s69-evk:n kanssa - Erich Styger
CM33 freeRTOS Esimerkki

kirjoittanut Theodor Rohde (Lobaro GmbH:n toimitusjohtaja) 4. toukokuuta 2019.

4 vastaa
  1. Daniel Oliveira sanoo:

    Hei Theodor,

    Kiitos tästä viestistä ja yksityiskohtaisesta selityksestä Armv8-M-arkkitehtuurista tietoturvalaajennuksineen. Btw, onko sinulla githubissa mitään gcc-pohjaisia projekteja, jotka liittyvät tähän aiheeseen?

    Tervehdys,
    Daniel

  2. Tobias sanoo:

    Hei Daniel,
    aiomme julkaista minimaalisen esimerkin nRF9160:lle käyttäen GCC:n CMSE-laajennuksia ja FreeRTOSia. Tämä on suunniteltu vaihtoehtoiseksi ympäristöksi SiP:lle. Uskon, että tämä vie noin 1 kuukauden.

    Tervehdys
    Theo

  3. Neculai Agavriloaie sanoo:

    Hei Theodor,

    Noin 20 vuotta MSP430:llä projisoinneissa, ja halusin tehdä muutoksen.
    Uudessa projektissani haluan käyttää Trustzone-teknologiaa, ja minun piti todella ymmärtää, miten se toimii.

    Tämä sivu on ainoa paikka, jossa toimintatapa on hyvin yksinkertainen ja selkeästi kuvattu.

    Tervehdys,
    Neculai

Kommentit on suljettu.