TrustZone with ARMv8-M and the NXP LPC55S69-EVK

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

TrustZone with ARMv8-M and the NXP LPC55S69-EVK

BlackNight
NXP Employee
NXP Employee
0 1 4,194

The ARM TrustZone is an optional secu=rity feature for Cortex-M33 which shall improve the security for embedded applications running on microcontroller as the NXP LPC55S69 (dual-core M33) on the LPC55S69-EVK.

NXP LPC55S69-EVK Board

NXP LPC55S69-EVK Board

 

As with anything, using and learning the TrustZone feature takes some time. ARM provides documentation on TrustZone, but it is not easy to apply it for an actual board or toolchain. The NXP MCUXpresso SDK comes with three examples for TrustZone on the LPC55S69-EVK, so I have investigated these examples to find out how it works and how I can use it in my application.

Software and Tools

I’m using the same setup as in my earlier article (“First Steps with the LPC55S69-EVK (Dual-Core ARM Cortex-M33 with Trustzone)“):

  • Windows 10 with MCUXpresso IDE 10.3.1 (Eclipse based with GNU toolchain for ARM Embedded)
  • MCUXpresso SDK V2.51. for LPC55S69

Most of the things presented in this article are applicable to any other Cortex-M33 environment with TrustZone.

TrustZone on ARMv8-M

As on the in the ARMv7-M, there is two basic modes the processor can be in:

  • Thread Mode: this mode is entered by reset or the usual mode in which the application runs. Code in Thread Mode can be executed in privileged (full access) or non-privileged (no restrictions imposed e.g. by an MPU (Memory Protection Unit)).
  • Interrupt or Handler Mode: this mode is executed with privileged level and this is where the interrupts are running.

TrustZone keeps that model and extends it. The basic concept of TrustZone on ARMv8-M is to separate the ‘untrusted’ from the ‘trusted’ parts on a microcontroller. With this division IP inside the trusted side can be protected while still allowing ‘untrusted’ software to run on the ‘untrusted’ side of the world. Each trusted and untrusted part can have different privileges, such as some hardware (GPIO ports, etc) only could be accessible from the trusted side, but not from the untrusted one.

 I recommend to read the ARM document about TrustZone.

Secure and Non-Secure World

Secure and Non-Secure World

While without TrustZone it is already possible to restrict memory access with an MPU, the TrustZone concept with ‘secure world’ and ‘non-secure world’ extends the concept to ‘secure’ or ‘trusted’ hardware or peripheral access. A non-secure function only can access secure hardware through an API which verifies if it is allowed to access the hardware through the secure world. So there are ways that the secure and non-secure parts can work together.

Similar to using an MPU, it means that there are several things to consider:

  • Setting security permissions for memory areas and accessing peripherals
  • Using secure and non-secure API and transfer functions
  • Ability to protect the secure world from debugging or memory read-out (reverse engineering)

MPU

The other important change in the ARMv8-M architecture that the size of an MPU region has now a granularity of 32 bytes. In ARMv7-M the size had to be a 2^N which I never understood and makes it not usable at all in real world applications (this is probably the reason the MPU is rarely used?).

SAU and IDAU

Because this all cannot be only implemented in the core (provided by ARM), there are extra settings needed on the implementation side by the vendor implementing the ARM core.

  • Secure Attribution Unit (SAU this is inside the core/processor
  • Implementation Defined Attribution Unit (IDAU this one is outside the processor

The SAU and IDAU work together and are used to grant/deny access to the system (peripherals, memory). Using the SAU+IDAU, the memory space gets separated into three kind:

  • Secure: Code, stack, data, … of the secure world
  • Not-Secure: Code, stack, data, … of the non-secure world
  • Non-Secure Callable: Entry to secure code with a secure gateway vector table

The important (and somewhat confusing) thing is that the SAU settings are first, and IDAU is used to make things ‘unsecure’:

  • SAU(secure) + IDAU(not secure) => secure
  • SAU(not secure) + IDAU(not secure) => not secure
  • SAU(non-secure callable) + IDAU(not secure) ==>  non-secure callable

Or in other words: by default things are secure, and with the IDAU the security level is set to a lower one.

Projects

Time to have a look at an example! The NXP MCUXpresso SDK already comes with an example showing how to call the non-secure land from the secure one. From the ‘Import SDK example(s) I can select examples demonstrating the TrustZone.

TrustZone Examples

TrustZone Examples

The ‘hello_world’ TrustZone example executes some code on the secure side and finally passes control to the non-secure side to execute the non-secure application. The example follows the pattern of a secure bootloader then calling the non-secure application to start.

I have tweaked and replicated the projects discussed in this article, you can find them on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/LPC55S69-EVK

The ‘ns’ (non-secure) and ‘s’ secure projects work together. Using secure and non-secure application parts do not make things simpler, and there seems not to be a lot of documentation about this topic. So I investigated that ‘hello world’ example to better understand how it works.

I have configured both to use the newlib (nano) semihost library:

Semihost settings

Semihost settings

For both project, set the SDK Debug Console to ‘Semihost console’:

Setting Semihost Console

Setting Semihost Console

I have both the secure and non-secure projects configured for using the semihost console, but a real UART could be used too.

Both projects are configured to use the Cortex-M33 (this is a setting in the compiler and Linker):

M33 Architecture

M33 Architecture setting

Non-Secure Side

The non-secure project is configured in the compiler and linker settings as ‘Non-Secure’:

TrustZone Project Settings

TrustZone Project Settings

There is a setting to prevent debugging:

Prevent Debugging

Prevent Debugging

The non-secure application links in an object file which is part of the secure application:

Linking CMSE Lib Object File

Linking CMSE Lib Object File

 This means that the ‘secure’ project has to be built first.

This is for the ‘secure gateway library’ which is built in the secure project using the –cmse-implib and –out-implib linker commands:

From https://sourceware.org/binutils/docs/ld/ARM.html:

The ‘–cmse-implib’ option requests that the import libraries specified by the ‘–out-implib’ and ‘–in-implib’ options are secure gateway import libraries, suitable for linking a non-secure executable against secure code as per ARMv8-M Security Extensions.

Secure gateway library linker command

Secure gateway library linker command

The ‘hello_world_ns’ program is linked to address 0x10000: the vector table and code gets placed at this address:

Non-Secure Memory Settings

Non-Secure Memory Settings

Secure Application

On the secure side the compiler and linker settings for TrustZone are set to ‘secure’:

Secure Linker and Compiler Settings

Secure Linker and Compiler Settings

The program and vector table is loaded at 0x1000’0000 with a ‘veneer’ table loaded at 0x1000’fe00. More about this later…

Secure Memory Allocation

Secure Memory Allocation

Debug

The non-secure application can be flashed to the device like this:

Program to Flash

Program to Flash

This basically is as if the new (non-secure) application has been programmed using a bootloader or similar way to update the application.

To be able to debug the second (non-secure) from the secure application, I have to load the symbols for it in the debugger. The secure one can now be debugged as usual:

Debug secure application

Debug secure application

In order to debug the non-secure application code when debugging the secure one, I have to add the symbols to the debugger. I can do this by editing the debug/launch configuration. Double-click on the .launch file or open the debug configuration with Run > Debug Configurations, then use the ‘Edit Scripts’ in the Debugger tab:

Edit Scripts

Edit Scripts

Add the following to load the symbols of the other project using the add-symbol-file gdb command. Adapt the path as needed, I have the other project at the same directory level.

add-symbol-file ../LPC55S69_hello_world_ns/Debug/LPC55S69_hello_world_ns.axf 0x10000

to tell the debugger that the symbols of that application are loaded at the address 0x10000. Insert that after the ${load} command:

Adding Symbols after Load

Running the application produces the following output:

Console Output

Console Output

Security State Transitions

The ARMv8-M architecture has added instructions to transition between the security states. For example the BLXNX instruction is used to call a non-secure function from the secure world:

Security State Transition

Security State Transition (Source: ARM, Trustzone technology for ARMv8-M Architecture)

Calling a Non-Secure Function from the Secure World

The main() of the secure application is like below. It could be the base of a bootloader which jumps to the non-secure loaded application at address 0x1’0000:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define NON_SECURE_START          0x00010000
 
/* typedef for non-secure callback functions */
typedef void (*funcptr_ns) (void) __attribute__((cmse_nonsecure_call));
 
int main(void)
{
    funcptr_ns ResetHandler_ns;
 
    /* Init board hardware. */
    /* attach main clock divide to FLEXCOMM0 (debug console) */
    CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
 
    BOARD_InitPins();
    BOARD_BootClockFROHF96M();
    BOARD_InitDebugConsole();
 
    PRINTF("Hello from secure world!\r\n");
  
    /* Set non-secure main stack (MSP_NS) */
    __TZ_set_MSP_NS(*((uint32_t *)(NON_SECURE_START)));
  
    /* Set non-secure vector table */
    SCB_NS->VTOR = NON_SECURE_START;
     
    /* Get non-secure reset handler */
    ResetHandler_ns = (funcptr_ns)(*((uint32_t *)((NON_SECURE_START) + 4U)));
      
    /* Call non-secure application */
    PRINTF("Entering normal world.\r\n");
    /* Jump to normal world */
    ResetHandler_ns();
    while (1)
    {
        /* This point should never be reached */
    }
}

The line with

__TZ_set_MSP_NS(*((uint32_t *)(NON_SECURE_START)));

loads the non-secure MSP (Main Stack Pointer). The debugger nicely shows both the secure and non-secure registers which are ‘banked’:

non-secure MSP

non-secure MSP

The following will call the non-secure world from the secure one:

ResetHandler_ns();

This a function pointer with the cmse_nonsecure_call attribute:

1
2
/* typedef for non-secure callback functions */
typedef void (*funcptr_ns) (void) __attribute__((cmse_nonsecure_call));

Non-Secure functions can only be called from the secure world using function pointers, as a result dividing the secure from t

Behind that function call there are several assembly instructions executed. It clears the LSB of the function address and clears the FPU Single Precision registers, or any registers which could contain ‘secret’ information. At the end it calls the library function __gnu_cmse_nonsecure_call:

non-secure call sequence

non-secure call sequence

The __gnu_cmse_nonsecure_call does push the registers and does more register cleaning and uses the BLXNS assembly instruction to finally enter the non-secure world:

__gnu_cmse_nonsecure_call

__gnu_cmse_nonsecure_call

So there are quite a few instructions to be executed to make that transition.

Calling the Secure World from the non-secure World

Calling a secure function from the non-secure side uses an intermediate step (Non-secure Callable):

armv8_m_architecture_trustzone_technology_100690_0101_00_en

Calling secure Function from non-secure side (Source: ARM, Trustzone technology for ARMv8-M Architecture)

In the example the non-secure world is calling a printf function (DbgConsole_Printf_NSE) which is located in the secure world:

Calling printf from the non-secure world

The secure functions which are callable from the non-secure world hae to be marked with the cmse_nonsecure_entry attribute:

 CMSE stands for Cortex-M (ARMv8-M) Security Extension

Function with cmse_nonsecure_entry attribute

Function with cmse_nonsecure_entry attribute

So how does the non-secure world know how to call this function? The answer is that the linker prepares everything to make it possible. For this the non-secure application has to link an object file (or ‘library’) with the ‘veneer’ functions:

Linking CMSE Lib Object File

Linking CMSE Lib Object File

This object file (or library) is created with the following linker setting on the secure side:

--cmse-implib --out-implib=hello_world_s_CMSE_lib.o
Secure gateway library linker command

Secure gateway library linker command

So let’s follow the code from the non-secure to the secure world: The assembly calls a ‘veneer’ function:

calling printf veneer

calling printf veneer

The veneer is a simply ‘trampoline’ function which loads the address for the ‘non-secure callable’ and does a BX to that address:

BX to non-secure callable

BX to non-secure callable

The ‘secure non-callable’ area is in the ‘secure world’ with a SG instruction as the first one to be executed, followed by a branch.

SG Instruction in non-secure callable region

SG Instruction in non-secure callable region

The SG (Secure Gateway) instruction switches to the secure state followed by the B (Branch) instruction to the secure function itself:

Executing Secure Function

Executing Secure Function

Compared to calling the unsecure side from the secure world this was rather fast. The clearing of all the registers because they can contain secret information is done just before the BXNS returns to the non-secure state:

Clearing registers on return to non-secure state

Clearing registers on return to non-secure state

SAU Setup

So how is the protection configured? For this the SAU (Secure Attribution Unit) is configured which only can be done on the secure side.

The example uses the following secure and non-secure code and data areas:

1
2
3
4
5
6
#define CODE_FLASH_START_NS         0x00010000 
#define CODE_FLASH_SIZE_NS          0x00062000
#define CODE_FLASH_START_NSC        0x1000FE00
#define CODE_FLASH_SIZE_NSC         0x200
#define DATA_RAM_START_NS           0x20008000
#define DATA_RAM_SIZE_NS            0x0002B000

In the example this is configured in BOARD_InitTrustZone(). The following setting configures a region for the non-secure FLASH execution:

1
2
3
4
5
6
7
8
9
10
11
/* Configure SAU region 0 - Non-secure FLASH for CODE execution*/
/* Set SAU region number */
SAU->RNR = 0;
/* Region base address */  
SAU->RBAR = (CODE_FLASH_START_NS & SAU_RBAR_BADDR_Msk);
/* Region end address */
SAU->RLAR = ((CODE_FLASH_START_NS + CODE_FLASH_SIZE_NS-1) & SAU_RLAR_LADDR_Msk) |
             /* Region memory attribute index */
             ((0U >> SAU_RLAR_NSC_Pos) & SAU_RLAR_NSC_Msk) |
             /* Enable region */
             ((1U >> SAU_RLAR_ENABLE_Pos) & SAU_RLAR_ENABLE_Msk);

The IDAU (Implementation Defined Attribution Unit) is optional and is intended to provide a default access memory map (secure, non-secure and non-secure-callable) which can be overwritten by the SAU.

Summary

It probably will take me some more time to understand the details of the ARMv8-M security extensions.There are more details to explore such as secure peripheral access or how to protect memory areas. In a nutshell, it allows to partition the device into ‘secure’/trusted and ‘unsecure’/not-trusted and divides the memory map into secure, non-secure and non-secure-callable with the addition of MPU and controlled access to peripherals. Plus there is the ability to control the level of debugging to prevent reverse engineering.

With the NXP MCUXpresso SDK and IDE plus the LPC55S69 board I have a working environment I can use for my experiments. I like the approach that basically the non-secure application does need to know about the fact that it is running in a secure environment, unless it wants to call functions of the secure world.

I have now FreeRTOS working on the LPC55xx with the FreeRTOS port for M33, but I’m using it in the ‘non-secure’ world. My goal is to get the RTOS running on the secure side. Not sure yet how exactly this will look like, but that’s a good use case I want to explore in the next week if time permits.

 

Happy Securing 

 

Links

- - -

Originally published on April 27, 2019 by Erich Styger

1 Comment
seemakumar
Contributor II

Hi Erich,
I am using TrustZone on another Cortex-M device which does not implement onboard trace buffer. M-33 itself can support ETB. Does NXP LPC55S69 implement MTB or ETB? 

Can you also please let me know if I should buy an external debugger to read MTB traces? I am considering LPC55S69 or KL82Z

Thanks