Executing secure functions from non-secure code using Arm TrustZone - Embedded.com

Executing secure functions from non-secure code using Arm TrustZone


The traditional embedded software application exists in a single contiguous space with unique ID’s, memory and code all sitting together and easily accessible. This of course makes it very easy for a hacker to access the entire system as soon as they can get their foot in the door. The key to embedded system security is isolation. A new way that developers can improve isolation is to utilize the new TrustZone® capabilities in Arm® M23/33 microcontrollers. In this post, I’m going to walk the reader through how they can protect secure functions using TrustZone but still access them from a non-secure memory region.

Before diving into the details, it would be useful to discuss a little bit about TrustZone. TrustZone is a security hardware extension for the Armv-8 architecture that provides hardware isolation that creates a secure and non-secure zone from which software can be executed. Both regions have their own MSP, PSP, MPU’s and the secure region even has some non-banked registers that are not accessible in the non-secure zone. The transition from secure to non-secure and vice versa is all done in hardware within three clock cycles, so a developer doesn’t need to concern themselves with adding any extra code to handle the transition; However, a developer does need to concern themselves with properly controlling which secure functions can be accessed from the non-secure region.

Figure 1 – Arm TrustZone creates a hardware isolation between secure and non-secure code. TrustZone allows developers to securely boot their microcontroller in addition to specifying which memory regions, interrupts and peripherals should execute in the secure zone. (Image Source: Keil Appnote 291)

By default, code executing in the non-secure region cannot access the secure region. Any attempt to dereference a memory location in the secure zone will result in an exception. The problem is that a developer may need to call a secure function such as a crypto library function or maybe a peripheral driver that has been deemed secure. In order to do this, a developer needs to create an interface in their secure application project that becomes part of a secure gateway between the non-secure and secure code regions and allows the secure function to be executed. The secure function executes in the secure or trusted zone, keeping it isolated from the non-secure code.

Creating the interface between the secure code and the non-secure code is done using the arm_cmse library which is a C library extension to support secure executable files. The library contains definitions for creating function pointers to non-secure memory such as cmse_nsfptr_create. The compiler itself, such as Keil MDK, also include new compiler attributes like cmse_nonsecure_entry which tell the compiler that the associated function is located in secure memory but accessible from non-secure memory.

The process for creating an accessible secure function is simple. First, a developer defines their secure function just like they would any other function except that the function is defined within their secure project. An example secure function can be seen below:

int MySecureFunction(funcptr_ns callback, int Data){   funcptr_NS callback_NS;   callback_NS = (funcptr_NS)cmse_nsfptr_create(callback);   Data = callback_NS(Data);   return Data;}

There are several interesting points to note about this code:

  • The code makes use of the funcptr_NS type from arm_cmse.h which defines a type for function pointers accessing non-secure memory
  • Accessing a non-secure callback function from the secure zone requires calling cmse_nsfptr_create which will allow the non-secure function to be accessed in the secure region
  • TrustZone code will typically use NS to denote non-secure and S to denote secure
  • As written, this code would not be accessible to the non-secure region

In order to remedy the fact that this code is completely secure and non-accessible, a developer needs to use the cmse_nonsecure_entry attribute. The secure function definition would then change from:

int MySecureFunction(funcptr_ns callback, int Data){…}


int MySecureFunction(funcptr_ns callback, int Data) __attribute__((cmse_nonsecure_call)){…}

Adding the attribute cmse_nonsecure_call tells the compiler that this function is accessible from non-secure memory. This will allow a secure gateway instruction to be used to access the function. In addition, when the secure function has completed execution and is ready to jump back to the non-secure code, any non-banked registers, those that are being shared between the secure and non-secure regions, will be cleared. Clearing the registers prevents any secure data that may have been stored in the non-banked registers from being exposed to the non-secure zone.

At this point, a developer would be able to compile the secure library and then from their non-secure project, include the header file for their function and make calls to MySecureFunction.

The ability to isolate critical functions from the rest of the application is critical when attempting to secure an embedded system. Careful thought should be applied up front to decide which functions in an application should be isolated and even which mechanisms will be used to. TrustZone is one tool that embedded software developers can start to utilize to improve their products security.


Jacob Beningo is an embedded software consultant, advisor and educator who currently works with clients in more than a dozen countries to dramatically transform their software, systems and processes. Feel free to contact him at jacob@beningo.com, at his website www.beningo.com, and sign-up for his monthly Embedded Bytes Newsletter.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.