Koncepce zóny důvěryhodnosti ARMv8-M
Použití GCC CMSE (Cortex-M Security Extensions) s jádry ARM Cortex-M33 a M23
Úvod a motivace
Tento článek je výsledkem mých prvních kroků s novým nRF9160 Systém v balíčku společnosti Nordic Semiconductor. Tento SiP lze použít k vytvoření senzorových uzlů IoT s mobilním připojením k internetu. Integruje modem pro nové "IoT optimalizované" nízkopříkonové varianty standardu LTE, konkrétně NB.IoT a LTE-M, které mají nahradit tradiční připojení M2M pomocí sítí 2G.
Z koncepčního hlediska je pro nás v Lobaro SiP velmi zajímavý, protože se vymyká rozšířenému pojetí, kdy jeden aplikační mikroprocesor komunikuje po sériové lince pomocí AT příkazů se samostatným mobilním modemem. Kromě toho využívá nejnovější jádro procesoru ARM Cortex-M33 s novými bezpečnostními funkcemi zaměřenými zejména na bezpečné aplikace IoT. Modem LTE je přímo integrován do SiP a komunikuje s aplikačním procesorem pomocí meziprocesorové komunikace založené na sdílené paměti. Toto těsné propojení umožňuje kromě snadnějšího zabezpečení uzlu IoT také lepší optimalizaci nízké spotřeby a menší rozměry PCB.
Procesor Cortex-M33 uvnitř nRF9160 využívá novou architekturu ARMv8-M, která nabízí novou funkci, tzv. "ARM TrustZone". Následující článek odráží můj výklad základních konceptů a jejich praktickou aplikaci pomocí překladače GNU ARM GCC a jeho funkcí CMSE (=Cortex M Security Extensions). Vzhledem k tomu, že v současné době (květen 2019) není na internetu k nalezení mnoho informací, doufám, že vám tento článek pomůže k rychlejšímu začátku do tématu, než jsem měl já.
Ačkoli používám nRF9160 Cortex-M33 SiP, většina konceptů uvedených v následujícím textu platí i pro jiná zařízení Cortex-M33 a Cortex-M23 s podporou ARMv8-M od různých výrobců křemíku. Totéž platí pro použití jiných kompilátorů ARM než GCC.
Jádro Cortex-M33, (c) ARM®
Zabezpečené a nezabezpečené provozní stavy CPU
Kromě klasických provozních stavů CPU Thread (normální kód) a Handler (IRQ/Exception kód) zavádí architektura ARMv8-M bezpečné a nezabezpečené atributy. Po resetu se CPU vždy spustí v zabezpečeném režimu a může přistupovat ke všem oblastem paměti a používat libovolné periferie bez omezení. To je jako běžný provozní režim starších architektur, jako je Cortex-M4 nebo Cortex-M3. Váš procesor založený na architektuře ARMv8-M může zůstat v tomto režimu i bez použití funkcí TrustZone, ale pak je vytváření zabezpečených aplikací obtížnější. Musíte dvakrát zkontrolovat každou část kódu, protože v případě chyby může (vzdálený) útočník získat přístup ke kompletnímu systému a/nebo k uloženým pověřením.
Myšlenkou zóny TrustZone je skrýt bezpečnostní kód, data a periferní zařízení v malé oblasti zařízení. Tato oblast bude označována jako "bezpečná", zatímco ostatní části zařízení budou považovány za (potenciálně) "nezabezpečené". Rozdělení oblastí paměti, přerušení a periferií na bezpečné a nezabezpečené je na vás. Obvykle vaše první řádky kódu po zapnutí provedou tato rozhodnutí na základě potřeb vaší aplikace. V části "Správce zabezpečených diskových oddílů"příklad kódu pro nRF9160 Nordic se rozhodl použít oddíl paměti flash o velikosti 256 kB secure a 768 kB non-secure.
Jádro procesoru udržuje dvě bankovní verze (zabezpečenou/nezabezpečenou) registrů důležitých pro provádění, např. ukazatele zásobníku a řídicí blok systému (SCB). Aktuální bezpečnostní režim procesoru určuje, která varianta registru bude použita. Podobně je bankovní mechanismus přítomen u některých periferií (např. SysTick), které jsou hardwarově implementovány dvakrát. Zabezpečený program má vždy přístup k oběma verzím. Po nastavení ukazatele na nezabezpečený zásobník a umístění adresy nezabezpečeného vektoru použije zabezpečený zaváděcí kód funkční ukazatel na tabulku vektorů nezabezpečeného firmwaru. Od tohoto okamžiku procesor spustí nezabezpečený firmware.
Nezabezpečený firmware nesmí vědět o existenci zabezpečeného firmwaru. Může být "normálně" vyvinut a nasazen jako na starších jádrech, přičemž jeho linkerový skript směřuje pouze do definovaných nezabezpečených oblastí paměti. Po skoku do nezabezpečeného firmwaru běží procesor v nezabezpečeném režimu a nemůže již přímo přistupovat k žádnému zabezpečenému kódu, datům nebo periferiím, aniž by vyvolal bezpečnostní výjimku. Jediným platným způsobem, který přetrvává pro přístup k zabezpečeným funkcím z nezabezpečeného kódu, je volání speciálních funkcí brány / vstupu / okleštění, které může poskytnout zabezpečená strana. Zbytek tohoto článku podrobně ukazuje, jak to funguje, s využitím ARM GCC a jeho implementace CMSE (Cortex M Security-Extensions).
Nezabezpečené oblasti paměti (NSC) a instrukce ASM pro zabezpečenou bránu (SG)
Aby bylo možné volat nezabezpečené -> zabezpečené funkce, musí být některá (malá) část zabezpečené oblasti flash v kódu zabezpečeného firmwaru nakonfigurována jako "Non-secure callable" (NSC). U nRF9160 se to provádí pomocí periferie SPU.
Jak již bylo popsáno, nezabezpečený kód nemůže volat funkce v zabezpečené paměťové doméně, aniž by v jádře ARMv8-M vyvolal výjimku. Označením malé části zabezpečené paměti jako NSC se toto omezení snižuje tak, že volání/odkazy do míst obsahujících instrukci "SG" (Secure Gateway) v assembleru jako první opcode jsou nyní povoleny. Z místa za instrukcí SG program provede další větvení do implementace bezpečných funkcí ležících v bezpečnější paměti, např. neoznačené jako NSC. Volání zabezpečené funkce z takto nezabezpečeného kódu je dvoustupňový proces s řetězovými skoky nejprve do paměti označené NSC (pomocí instrukce SG) a poté se pokračuje skokem do vlastního těla funkce v zabezpečené paměti. Tímto postupem jsou vstupy do zabezpečené paměti odděleny od zbytku zabezpečené paměti. Důvod, proč nepoužívat instrukci SG přímo na začátku funkcí v zabezpečené paměti a vyhnout se dvoukrokovému procesu, vysvětluje technická podpora ARM zde: Důvody pro zavedení regionů NSC.
Definice nezabezpečených volatelných funkcí v zabezpečeném firmwaru
Kompilátory ARMv8-M naštěstí před programátorem skrývají předchozí popsaný dvoukrokový proces při volání zabezpečené funkce z nezabezpečeného firmwaru. Zabezpečená funkce určená jako vstupní bod pro nezabezpečený firmware musí být po své deklaraci v zabezpečeném firmwaru označena atributem nezabezpečený vstup:
// nějaký c soubor projektu zabezpečeného firmwaru definující funkce brány veneer // musí být zkompilován s příznakem -mcmse gcc (!) #include "arm_cmse.h" __atribut__((cmse_nonsecure_entry)) void ControlCriticalIO(){ // provést některé kritické věci }
Tím se kompilátoru přikazuje, aby automaticky vygeneroval dvě části kódu pro výše popsaný proces, pokud se kompiluje pomocí "-mcmse"Příznak ARM gcc.
// Příznaky kompilátoru GCC používané při sestavování bezpečné strany arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]
Vlastní tělo funkce umístí linker jako obvykle do zabezpečené sekce .text, ale část s instrukcemi SG a větvení umístí do zvláštní sekce nazvané ".gnu.sgstubs"nebo podobně pro kompilátory, které nejsou GNU C.
Malým funkcím přesměrování v oblasti NSC se také říká "okleštěné" funkce nebo "SG nudges", protože jsou tak malé, že provádějí pouze jednu SG instrukci a větvení. Soubor linkeru zabezpečeného firmwaru musí před skokem do nezabezpečeného firmwaru umístit veškerý kód veneer do zabezpečené oblasti paměti, kterou zabezpečený firmware prohlásí za NSC jako součást svého zabezpečeného zaváděcího procesu.
// Sekce Linkerscript pro dýhy TrustZone Secure Gateway .gnu.sgstubs : ALIGN (32) { . = ALIGN(32); _start_sg = .; *(.gnu.sgstubs*) . = ALIGN(32); _end_sg = .; } > FLASH-REGION-WITH-NSC-ENABLED
Volání zabezpečených funkcí z nezabezpečeného firmwaru (NS->S)
Obvykle se zabezpečený firmware vyvíjí a flashuje nezávisle na nezabezpečené bitové kopii firmwaru. Chcete-li použít jakoukoli funkci zabezpečeného firmwaru, musí být do zdrojů nezabezpečeného firmwaru zahrnut hlavičkový soubor (.h) popisující volatelné funkce zabezpečené strany. Kromě toho musí nezabezpečený firmware vědět, kde byly dříve v částech bezpečné paměti NSC umístěny vstupní body dýhy. To se provádí propojením speciálního objektového souboru (např. CMSE_importLib.o) s nezabezpečeným firmwarem, který obsahuje potřebné adresní informace. Tento objektový soubor musel být vytvořen během procesu linkování zabezpečeného firmwaru pomocí následujících voleb linkeru cmse:
// Příznaky GCC LINKER používané při bočním linkování SECURE arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]
Volitelný příkaz "-in-implib=CMSE_importLib.o" lze použít k tomu, aby položky již definované ve starších verzích objektového souboru zůstaly na stejném místě v paměti i v nové verzi. To umožňuje přidávat do zabezpečeného obrazu, aniž by bylo nutné aktualizovat i nezabezpečený obraz (který mohl být předtím spojen se starší verzí importní lib).
Pomocí souboru .h a propojením s objektem CMSE_importLib.o může nyní nezabezpečený firmware vyvolat funkci ControlCriticalIO() z výše uvedeného příkladu.
// Příznaky GCC LINKER používané při nezabezpečeném bočním linkování arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]
Volání nezabezpečených (zpětných) funkcí ze zabezpečeného firmwaru (S->NS)
Tento článek ukázal, jak lze pomocí ARM TrustZone vytvořit dvě izolované části firmwaru propojené dobře definovanými drobnými funkcemi brány / dýhy. Ty umožňují volat funkce zabezpečeného firmwaru z nezabezpečeného firmwaru.
Opačný směr, tj. bezpečný firmware volající nezabezpečenou funkci, funguje hned, pokud je adresa nezabezpečené funkce nějakým způsobem předána bezpečnému kódu. Obvykle se to dělá tak, že se definují zabezpečené funkce brány s ukazatelem na nezabezpečené zpětné volání jako parametrem:
// nějaký c soubor projektu zabezpečeného firmwaru definující funkce brány veneer // musí být zkompilován s příznakem -mcmse gcc (!) #include "arm_cmse.h" typedef void (*funcptr_ns) (void) __atribut__((cmse_nonsecure_call)); void ControlCriticalIO(funcptr_ns callback_fn) __attribute__((cmse_nonsecure_entry)){ funcptr_ns cb = callback_fn; // zachraňte volatilní ukazatel před nezabezpečeným kódem // zkontroluj, zda je daný ukazatel na nezabezpečenou paměť skutečně nezabezpečený, jak se očekávalo cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE); if (cb != 0) { /* proveďte některé kritické věci, např. použijte jiné bezpečné funkce */ cb(); // vyvolat nezabezpečenou funkci zpětného volání }else { // nedělej nic, pokud je ukazatel nesprávný } }
Atribut "nonsecure_call" je potřebný k tomu, aby bezpečný kompilátor vymazal nebankované registry pro obecné účely před tím, než přejde zpět do nezabezpečeného kódu, protože v opačném případě by to představovalo potenciální bezpečnostní riziko. Kromě toho dává kompilátoru pokyn, aby vymazal LSB adresy ukazatelů funkcí v základní instrukci branch assembleru. Tím procesor přejde v rámci větvení ze zabezpečeného do nezabezpečeného stavu.
Protože se s daným nezabezpečeným zpětným voláním musí zacházet jako s proměnnou volatile, jejíž obsah může být měněn nezabezpečenými přerušeními, je zapotřebí kopie ukazatele funkce "cb". Před vyvoláním zpětného volání je třeba zkontrolovat, zda jeho ukazatel ukazuje pouze do nezabezpečené paměti. Pokud by ukazoval na zabezpečenou paměť, představovalo by to rovněž bezpečnostní riziko.
Registr jádra Cortex-M33, (c) ARM®
Tipy a další literatura
Mějte na paměti, že linkovací skript pro zabezpečenou stranu musí brát v úvahu sekci pro funkce dýhy a linkovací skript pro nezabezpečenou stranu musí zohlednit oddíly paměti vytvořené v zabezpečeném firmwaru. Pokud jsou do archivního souboru (*.a) zahrnuty nezabezpečené volatelné funkce, musí být linkován s volbou linkeru "celý archiv" do zabezpečeného firmwaru. Pouze tak bude importní lib CMSE obsahovat potřebné dýhové funkce.
Několik úskalí psaní bezpečného kódu ARMv8-M - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
Trustzone s ARMv8-m a NXP lpc55s69-evk - Erich Styger
CM33 freeRTOS Příklad
napsal Theodor Rohde (generální ředitel společnosti Lobaro GmbH) dne 4. května 2019.
Ahoj Theodore,
Děkuji za tento příspěvek a podrobné vysvětlení architektury Armv8-M s bezpečnostními rozšířeními. Btw, máte na githubu nějaké projekty založené na gcc, které se týkají tohoto tématu?
Zdravím,
Daniel
Dobrý den, Danieli,
plánujeme vydat minimální příklad pro nRF9160 s použitím rozšíření GCC CMSE a FreeRTOS. To má sloužit jako alternativní prostředí pro SiP. Myslím, že to bude trvat přibližně 1 měsíc.
Pozdravy
Theo
Ahoj Theodore,
Po zhruba 20 letech projektování s MSP430 jsem chtěl udělat změnu.
Ve svém novém projektu chci použít technologii Trustzone a opravdu jsem potřeboval pochopit, jak funguje.
Tato stránka je jediným místem, kde je způsob ovládání velmi jednoduše a srozumitelně popsán.
Zdravím,
Neculai
Děkuji :-)