Conceptos en los que se basa ARMv8-M TrustZone

Uso de GCC CMSE (Cortex-M Security Extensions) con núcleos ARM Cortex-M33 y M23

Introducción y motivación

Este artículo es el resultado de mis primeros pasos con el nuevo nRF9160 Sistema en paquete de Nordic Semiconductor. Este SiP puede utilizarse para crear nodos sensores IoT con conexión celular a Internet. Integra un módem para las nuevas variantes de bajo consumo "optimizadas para IoT" de la norma LTE, a saber, NB.IoT y LTE-M, cuyo objetivo es sustituir la conectividad M2M tradicional mediante redes 2G.

Conceptualmente, el SiP es muy interesante para nosotros en Lobaro porque rompe con el concepto generalizado de tener un microprocesador de aplicación que habla a través de la línea serie utilizando comandos AT con un módem celular independiente. Además, utiliza el último núcleo de CPU ARM Cortex-M33 con nuevas funciones de seguridad dirigidas a aplicaciones IoT especialmente seguras. El módem LTE está integrado directamente en el SiP y se comunica con el procesador de aplicaciones mediante una comunicación entre procesadores basada en memoria compartida. Este estrecho acoplamiento permite una mejor optimización del bajo consumo y un menor tamaño de la placa de circuito impreso, además de facilitar la seguridad del nodo IoT.

El procesador Cortex-M33 del nRF9160 utiliza la nueva arquitectura ARMv8-M, que ofrece una nueva función llamada "Zona de confianza ARM". El siguiente artículo refleja mi interpretación de los conceptos subyacentes y su aplicación práctica utilizando el compilador GNU ARM GCC y sus características CMSE (=Cortex M Security Extensions). Dado que actualmente (Mayo 2019) no hay mucha información que encontrar online espero que este artículo pueda ayudarte a tener un comienzo más rápido en el tema que el que yo tuve.

Aunque estoy utilizando el nRF9160 Cortex-M33 SiP la mayoría de los conceptos que se proporcionan a continuación también son válidos para otros dispositivos Cortex-M33 y Cortex-M23 habilitados para ARMv8-M de diferentes proveedores de silicio. Lo mismo es válido para el uso de otros compiladores ARM distintos de GCC.

Núcleo Cortex-M33, (c) ARM

Estados de funcionamiento seguro y no seguro de la CPU

Además de los estados operativos clásicos de la CPU Thread (código normal) y Handler (código IRQ/Exception), la arquitectura ARMv8-M introduce atributos seguros y no seguros. Tras el reinicio, la CPU siempre se inicia en modo seguro y puede acceder a todas las regiones de memoria y utilizar cualquier periférico sin restricciones. Es como el modo de funcionamiento normal de arquitecturas más antiguas como Cortex-M4 o Cortex-M3. Tu CPU basada en ARMv8-M puede permanecer en este modo sin usar las características de TrustZone pero entonces la creación de aplicaciones seguras se hace más difícil. Tienes que comprobar dos veces cada parte de tu código, porque si hay un fallo un atacante (remoto) puede obtener acceso a todo tu sistema y/o a las credenciales almacenadas.

La idea de TrustZone es ocultar código, datos y periféricos relevantes para la seguridad en una pequeña región del dispositivo. Esta región se denominará "segura", mientras que las demás partes del dispositivo se tratarán como (potencialmente) "no seguras". La partición de las regiones de memoria, interrupciones y periféricos en seguras y no seguras depende de ti. Normalmente, tus primeras líneas de código tras el encendido tomarán estas decisiones basándose en las necesidades de tu aplicación. En la sección "Gestor de particiones seguro" para el nRF9160 Nordic decidió utilizar una partición de memoria flash de 256kB segura y 768kB no segura.

El núcleo de la CPU mantiene dos versiones con bancos (seguro / no seguro) de los registros relevantes para la ejecución, por ejemplo, los punteros de la pila y el bloque de control del sistema (SCB). El modo de seguridad actual de la CPU define qué variante de registro se utilizará. El mecanismo de banca está presente de forma similar para algunos periféricos (por ejemplo, SysTick) que se implementan dos veces en hardware. El programa seguro siempre puede acceder a ambas versiones. Después de configurar el puntero de pila no seguro y la ubicación de la dirección del vector no seguro, el código de arranque seguro utiliza un puntero de función a la tabla de vectores del firmware no seguro. A partir de este punto, la CPU ejecuta el firmware no seguro.

El firmware no seguro no debe conocer la existencia del firmware seguro. Puede ser "normalmente" desarrollado y desplegado como en núcleos antiguos con su linker-script apuntando sólo a las regiones de memoria no seguras definidas. Tras saltar al firmware no seguro, la CPU se ejecuta en modo no seguro y ya no puede acceder directamente a ningún código, dato o periférico seguro sin provocar una excepción de seguridad. La única forma válida que persiste para acceder a funciones seguras desde código no seguro es la invocación de funciones especiales de pasarela / entrada / carilla que la parte segura pueda proporcionar. El resto de este artículo demuestra cómo funciona esto en detalle usando el GCC de ARM y su implementación del CMSE (Cortex M Security-Extensions).

Regiones de memoria de llamada no segura (NSC) y la instrucción ASM de pasarela segura (SG)

Para permitir llamadas de funciones no seguras -> seguras, alguna (pequeña) parte de la región flash segura debe configurarse como "Non-secure callable" (NSC) en el código firmware seguro. En el nRF9160 esto se hace utilizando el periférico SPU.

Como se ha descrito anteriormente, el código no seguro no puede llamar a funciones en el dominio de la memoria segura sin que se produzca una excepción en el núcleo ARMv8-M. Al marcar una pequeña parte de la memoria segura como NSC, esta restricción se reduce de forma que ahora se permiten las llamadas / bifurcaciones en ubicaciones que contengan la instrucción de ensamblado "SG" (Secure Gateway) como primer opcode. Desde el punto posterior a la instrucción SG, el programa tomará otra bifurcación hacia la implementación de funciones seguras que se encuentra en la memoria segura más restringida, es decir, no marcada como NSC. La llamada a una función segura desde un código no seguro como éste es un proceso de dos pasos con saltos encadenados primero a la memoria marcada como NSC (con la instrucción SG) y luego con un salto al cuerpo de la función real en la memoria segura. Al utilizar este proceso, las entradas a la memoria segura se separan del resto de la memoria segura. La razón para no usar la instrucción SG directamente al principio de las funciones en la memoria segura y evitar el proceso de dos pasos es explicada por el soporte técnico de ARM aquí: Razones para introducir las regiones NSC.

Definición de funciones invocables no seguras en firmware seguro

Afortunadamente, los compiladores ARMv8-M ocultan al programador el proceso de dos pasos descrito anteriormente cuando se llama a una función segura desde el firmware no seguro. Una función segura diseñada como punto de entrada para firmware no seguro tiene que ser marcada con el atributo de entrada no segura después de su declaración en el firmware seguro:

// algún archivo c del proyecto de firmware seguro que defina las funciones de la pasarela veneer
// debe ser compilado con -mcmse gcc flag (!)
#include "arm_cmse.h"
__attribute__((cmse_nonsecure_entry)) void ControlCriticalIO(){
// hacer algunas cosas críticas
}

Esto indica al compilador que genere automáticamente dos partes de código para el proceso descrito anteriormente si se compila con la opción "-mcmse"Bandera ARM gcc.

// Banderas de COMPILADOR GCC utilizadas durante la compilación de la parte segura
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

El cuerpo real de la función será colocado por el enlazador en la sección segura .text como de costumbre, pero la parte con las instrucciones SG y de bifurcación se colocará en una sección especial llamada ".gnu.sgstubs" o similar para compiladores C no GNU.

Las pequeñas funciones de redirección en la región NSC también se llaman funciones "veneer" o "SG stups", porque son tan pequeñas que sólo hacen una instrucción SG y una bifurcación. El archivo linker del firmware seguro debe colocar cualquier código veneer en la región de memoria segura que el firmware seguro declarará como NSC como parte de su proceso de arranque seguro antes de saltar al firmware no seguro.

// Sección Linkerscript para carillas TrustZone Secure Gateway
.gnu.sgstubs : ALINEACIÓN (32)
{
    . = ALIGN(32);
    _start_sg = ..;
    *(.gnu.sgstubs*)
    . = ALIGN(32);
    _end_sg = .;
} > FLASH-REGION-WITH-NSC-ENABLED

Llamada a funciones seguras desde firmware no seguro (NS->S)

Normalmente el firmware seguro será desarrollado y flasheado independientemente de la imagen del firmware no seguro. Para utilizar cualquier funcionalidad del firmware seguro, se debe incluir en las fuentes no seguras un archivo de cabecera (.h) que describa las funciones invocables del lado seguro. Además, el firmware no seguro tiene que saber dónde se han colocado antes los stubs del punto de entrada de la chapa en las partes NSC de la memoria segura. Para ello, se vincula un archivo de objetos especial (por ejemplo, CMSE_importLib.o) al firmware no seguro que contiene la información de direcciones necesaria. Este archivo de objetos debe haber sido generado durante el proceso de vinculación del firmware seguro utilizando las siguientes opciones del vinculador cmse:

// Banderas GCC LINKER utilizadas durante el enlazado lateral SEGURO
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

La opción "-in-implib=CMSE_importLib.o" puede utilizarse para mantener las entradas ya definidas en versiones anteriores del archivo objeto en la misma ubicación de memoria exacta también en la nueva versión. Esto permite añadir elementos a la imagen segura sin necesidad de actualizar también la imagen no segura (que puede haberse enlazado antes con una versión anterior de la librería de importación).

Usando el archivo .h y enlazando contra el objeto CMSE_importLib.o el firmware no seguro puede ahora invocar la función ControlCriticalIO() del ejemplo anterior.

// Banderas GCC LINKER utilizadas durante la vinculación lateral no segura
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

Llamada a funciones no seguras (callback) desde firmware seguro (S->NS)

Este artículo demostró como ARM TrustZone puede ser usado para crear dos partes de firmware aisladas conectadas por pequeñas funciones de gateway / veneer bien definidas. Esto permite llamar a funciones del firmware seguro desde el firmware no seguro.

La otra dirección, es decir, que el firmware seguro llame a una función no segura, funciona de forma inmediata si la dirección de la función no segura se proporciona de algún modo al código seguro. Normalmente, esto se hace definiendo funciones de pasarela seguras con un puntero de devolución de llamada no seguro como parámetro:

// algún archivo c del proyecto de firmware seguro que defina las funciones de la pasarela veneer
// debe ser compilado con -mcmse gcc flag (!)
#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; // guardar puntero volátil de código no seguro
 
 // comprueba si el puntero dado a memoria no segura es realmente no seguro como se esperaba
 cb = cmse_check_address_range(cb, sizeof(cb), CMSE_NONSECURE);
  
 if (cb != 0) {
    /* hacer algunas cosas críticas, por ejemplo, utilizar otras funciones seguras */
    cb(); // invocar función de devolución de llamada no segura
 }else {
   // no hacer nada si el puntero es incorrecto
 }
}

El atributo "nonsecure_call" es necesario para indicar al compilador seguro que borre los registros de propósito general no bancarizados antes de saltar de nuevo al código no seguro, ya que no hacerlo supondría un riesgo potencial para la seguridad. Además, indica al compilador que borre el LSB de la dirección de los punteros de función en la instrucción de ensamblador de bifurcación subyacente. De este modo, la CPU pasará de un estado seguro a uno no seguro dentro de la bifurcación.

Debido a que la llamada de retorno no segura debe ser tratada como una variable volátil, cuyo contenido puede ser cambiado por interrupciones no seguras, se necesita la copia del puntero de la función "cb". Antes de invocar la llamada de retorno, debe comprobarse que su puntero apunta únicamente a memoria no segura. Si apuntara a memoria segura también sería un riesgo para la seguridad.

Registro del núcleo Cortex-M33, (c) ARM®.

Consejos y lecturas complementarias

Tenga en cuenta que el linkerscript para el lado seguro debe considerar la sección para las funciones veneer y el linkerscript para el lado no seguro debe reflejar las particiones de memoria realizadas en el firmware seguro. Si se incluyen funciones invocables no seguras en un archivo comprimido (*.a), debe vincularse con la opción de vinculación "whole-archive" al firmware seguro. Sólo de esta manera la lib de importación CMSE incluirá las funciones de carilla necesarias.

Algunas complejidades de la escritura de código ARMv8-M Secure - ARM Limited
SAM-L11-Security-ReferenceGuide-AN-DS70005365A.pdf - Microchip Technology
Trustzone con ARMv8-m y el NXP lpc55s69-evk - Erich Styger
CM33 freeRTOS Ejemplo

escrito por Theodor Rohde (director general de Lobaro GmbH) el 4 de mayo de 2019.

4 comentarios
  1. Daniel Oliveira Dice:

    Hola Theodor,

    Gracias por este post y la explicación detallada sobre la arquitectura Armv8-M con extensiones de seguridad. Por cierto, ¿tienes algún proyecto basado en gcc en github relacionado con este tema?

    Saludos,
    Daniel

  2. Tobias Dice:

    Hola Daniel,
    planeamos lanzar un ejemplo mínimo para el nRF9160 usando extensiones GCC CMSE y FreeRTOS. Esto está planeado para ser un entorno alternativo para el SiP. Creo que esto tomará aprox. 1 mes a partir de ahora.

    Saludos
    Theo

  3. Neculai Agavriloaie Dice:

    Hola Theodor,

    Después de unos 20 años proyectando con MSP430, quería hacer un cambio.
    En mi nuevo proyecto quiero utilizar la tecnología Trustzone y necesitaba entender cómo funciona.

    Esta página es el único lugar donde el modo de funcionamiento es muy sencillo y está claramente descrito.

    Saludos,
    Neculai

Los comentarios están desactivados.