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 kovin paljon tietoa, toivon, että tämä artikkeli auttaa sinua pääsemään nopeammin alkuun aiheen parissa 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) CPU:n toimintatilojen lisäksi turvallisia ja ei-turvallisia attribuutteja. 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. Jokainen koodin osa on tarkistettava 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"koodiesimerkki nRF9160 Nordic päätti käyttää 256kB:n suojattua ja 768kB:n ei-turvattua flash-muistiosastoa.
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. Siirryttyään ei-turvalliseen laiteohjelmaan CPU toimii ei-turvallisessa tilassa, eikä se voi enää käyttää suoraan mitään turvallista koodia, dataa tai oheislaitteita ilman, että se laukaisee 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 suojatusta muistista NSC:ksi tätä rajoitusta lievennetää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 siis 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-turvattujen 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 viilun yhdyskäytävän 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 vastaavaa muille kuin GNU C-kääntäjille.
NSC-alueen pieniä uudelleenohjausfunktioita kutsutaan myös "viilutustoiminnoiksi" tai "SG-työntöiksi", koska ne ovat niin pieniä, että ne tekevät vain yhden SG-käskyn ja haarautumisen. Suojatun laiteohjelmiston linkitystiedoston on sijoitettava kaikki veneer-koodi suojatulle muistialueelle, jonka suojattu laiteohjelmisto ilmoittaa NSC:ksi osana suojattua käynnistysprosessia, ennen kuin se siirtyy ei-turvattuun laiteohjelmistoon.
// Linkerscript Section for 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 siirrytään takaisin ei-turvalliseen koodiin, koska 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 linkkikirjoituksen on otettava huomioon viilutustoimintojen osio ja ei-turvallisen puolen linkkikirjoituksen on otettava huomioon suojatussa laiteohjelmistossa tehdyt muistin osiot. Jos ei-turvallisia kutsuttavia toimintoja sisällytetään arkistoon (*.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.
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
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
Hei Theodor,
Noin 20 vuotta MSP430:llä projisoinneissa, ja halusin tehdä muutoksen.
Uudessa projektissani haluan käyttää Trustzone-teknologiaa, ja minun oli todella ymmärrettävä, miten se toimii.
Tämä sivu on ainoa paikka, jossa toimintatapa on hyvin yksinkertainen ja selkeästi kuvattu.
Tervehdys,
Neculai
Kiitos :-)