Skip to content

TrustZone configuration

Andrea Barisani edited this page Oct 25, 2021 · 32 revisions

The previous tutorial covers load and execution of code in TrustZone NonSecure World without any specific restrictions, accomplishing the bare minimum required for working (but unsafe) NonSecure execution.

We now fully explore TrustZone protections to ensure isolation of the Secure World execution context.

Introduction

The ARM Security Extensions, which are marketed as TrustZone (TZ), represent a technology aimed to replace the need for a separate dedicated security core. The extensions allow separation of the execution environment between two virtual processors, each implementing its own “world”.

The "Secure World" holds a secure execution context which is meant to performed sensitive operations and handle data which is not meant to be ever seen by the "Normal World" (or NonSecure World).

This domain separation is achieved by propagating the NS (NonSecure) bit throughout the system at both CPU core level and peripheral level.

Example use cases for TrustZone include protected encryption/decryption operations (to prevent main OS from ever seeing the key, such as in DRM uses).

The enforcement of TrustZone security happens fundamentally at two levels:

  • ARM core support: this represents a standardized layer, part of the ARM core architecture and identical to all cores sharing the same instruction set and featuring ARM security extensions.

  • Peripheral support: this is a vendor specific implementation which changes across vendors and even System-on-Chips (SoCs) of the same family.

ARM core support

The GoTEE execution context, implemented by the monitor package, provides TrustZone configuration support for the CPU.

To Load a NonSecure execution context the false boolean argument must be set:

os, err := monitor.Load(osELF, NonSecureStart, NonSecureSize, false)

When loading the execution context as NonSecure the processor state is automatically assigned to system mode.

If ARM co-processor access is required in NonSecure World, it can be allowed through the Non-Secure Access Control Register:

// grant access to CP10 and CP11
imx6.CPU.NonSecureAccessControl(1<<11 | 1<<10)

This execution context, when executed with Run will start in NonSecure World.

The context will yield back to the Trusted OS in Secure World if one of the following conditions arises:

  • the NonSecure triggers a monitor exception
  • a secure hardware interrupt is received (e.g. TrustZone aware timer)

Peripheral support: NXP i.MX6 family

On the NXP i.MX6 series peripheral access is controlled by the following components:

  • The Central Security Unit (CSU) allows to define restrictions to individual (or groups of) peripherals (e.g. can NonSecure access the USB controller?) as well defining the bus privilege level when memory access is made (e.g. is the USB controller seen as a Secure or NonSecure?).

⚠️ an inconsistent CSU configuration would allow protections bypass, for instance if the NonSecure World is allowed to access a peripheral which can originate Secure accesses then it would be able to execute DMA to Secure World memory. It is therefore vital to understand and configure the CSU correctly.

  • The TrustZone Address Space Controller (TZASC) monitors external memory accesses (e.g. DDR) and allows to protect Secure World memory from NonSecure accesses.

  • The internal RAM controller (OCRAM) is TrustZone aware and allows access configuration.

  • The TrustZone Watchdog (TZ WDOG) allows forced switching from NonSecure World to Secure World, to prevent Denial-of-Service scenarios.

Central Security Unit (CSU)

The TamaGo csu package provides support for CSU re-configuration.

A list of available peripherals is available in csu package constants.

The Config Security Level (CSL) defines restrictions for accessing individual (or groups of) peripherals (e.g. whether a peripheral can be accessed from NonSecure World), it can be set with SetSecurityLevel for a specific peripheral identifier and slave index.

Typically a NonSecure CSL is granted to all available peripherals before further restrictions are set:

// grant NonSecure access to all peripherals
for i := csu.CSL_MIN; i <= csu.CSL_MAX; i++ {
	csu.SetSecurityLevel(i, 0, csu.SEC_LEVEL_0, false)
	csu.SetSecurityLevel(i, 1, csu.SEC_LEVEL_0, false)
}

The Security Access (SA) defines the peripheral access policy (e.g. whether a peripheral makes bus accesses as Secure or NonSecure), it can be set with SetAccess for a specific peripheral group.

Typically a NonSecure SA is granted to all available peripherals before further allowances are set:

// set all controllers to NonSecure
for i := csu.SA_MIN; i < csu.SA_MAX; i++ {
	csu.SetAccess(i, false, false)
}

We can now proceed to lock down peripherals based on our security goals, on the NXP i.MX6 at minimum the following peripherals must be protected:

  • The ROM Controller (ROMCP) as it can be used to patch the internal ROM which might be used by Secure World or allow privileged DMA.

  • The TZASC (see next section) as it controls external memory protection and might be re-configured if it has not been locked down at first configuration.

As an additional example for the i.MX6ULZ we restrict GPIO4 as well as DCP access, to restrict LED use and device-unique key derivation only within Secure World.

⚠️ restricting visual feedback, such as LEDs, to Secure World is a convenient way to ensure to the user that some action is taking place securely. An example application is secure input (e.g. PIN/passphrase) prompt by Secure World, to exclude any "phishing" by malicious NonSecure code.

To prevent NonSecure access to these peripherals we re-configure them with security level 4 (exclusive Secure World R/W access):

// restrict access to ROMCP
csu.SetSecurityLevel(13, 0, csu.SEC_LEVEL_4, false)

// restrict access to TZASC
csu.SetSecurityLevel(16, 1, csu.SEC_LEVEL_4, false)

// restrict access to LEDs (GPIO4, IOMUXC)
csu.SetSecurityLevel(2, 1, csu.SEC_LEVEL_4, false)
csu.SetSecurityLevel(6, 1, csu.SEC_LEVEL_4, false)

// restrict access to DCP
csu.SetSecurityLevel(34, 0, csu.SEC_LEVEL_4, false)

Finally we must set the SA for the DCP, as we want it to perform DMA of input/output data buffers located in Secure World memory.

// set DCP as Secure
csu.SetAccess(14, true, false)

TZASC

The TamaGo tzasc package provides support for TZASC re-configuration.

By default the TZASC sets Secure World exclusive access to the entire memory (region 0), NonSecure World access must be allowed to the Main OS assigned memory region:

tzasc.EnableRegion(1, NonSecureStart, NonSecureSize, (1 << tzasc.SP_NW_RD) | (1 << tzasc.SP_NW_WR))

This TZASC re-configuration is automatically done by Load on all NonSecure execution contexts.

OCRAM

The internal RAM controller (OCRAM) on the i.MX6 is TrustZone aware, by default it allows both Secure and NonSecure access but it can be re-configured for exclusive Secure World access.

In TamaGo the internal RAM is used only as default DMA area, rather than re-configuring the OCRAM we can simply re-assign the DMA area to external memory already protected by the TZASC.

The GoTEE example does so with this memory layout here.

 dma.Init(SecureDMAStart, SecureDMASize)

Examples

The GoTEE example, when launched on real hardware such as the USB armory Mk II, implement the restrictions shown in this tutorial and demonstrates NonSecure operation before and after TrustZone restrictions are in effect.

The sequence of events is as follows:

  1. The Trusted OS (S) loads and launches the Trusted Applet (S) and Main OS (NS). The bare minimum set of permissions is configured to allow NonSecure World operation (e.g. insecure configuration).

  2. The Main OS (NS) attempts to use the DCP to derive a key, with success.

  3. The Main OS (NS) yields back to the Trusted OS (S) with a monitor call.

  4. The Trusted Applet (S) issues GoTEE system calls to obtain random numbers and perform an RPC call.

  5. The Trusted Applet (S) attempts to read privileged Trusted OS (S) memory, it fails and triggers a data abort exception

  6. The Trusted OS (S) re-launches the Main OS (NS) with TrustZone restrictions in place, including ones for the DCP.

  7. The Main OS (NS) attempts to use the DCP to derive a key, it fails and triggers a monitor exception.

  8. The Trusted OS (S) attempts to use the DCP to derive a key, with success.

Example:

00:00:00 PL1 tamago/arm (go1.17.1) • TEE system/monitor (Secure World)
00:00:00 PL1 loaded applet addr:0x94000000 size:4093296 entry:0x9406e3a4
00:00:00 PL1 loaded kernel addr:0x80000000 size:3767953 entry:0x8006ce14
00:00:00 PL1 waiting for applet and kernel
00:00:00 PL1 starting mode:SYS ns:true sp:0x00000000 pc:0x8006ce14
00:00:00 PL1 starting mode:USR ns:false sp:0x96000000 pc:0x9406e3a4
00:00:00 PL1 tamago/arm (go1.17.1) • system/supervisor (Normal World)
00:00:00 PL1 in Normal World is about to perform DCP key derivation
00:00:00 PL1 in Normal World successfully used DCP (e777b98dd28a4071a0c94821b7a1a4d1)
00:00:00 PL1 in Normal World is about to yield back
00:00:00    r0:00000000  r1:814223f0  r2:00000001  r3:00000000
00:00:00    r4:00000000  r5:00000000  r6:00000000  r7:00000000
00:00:00    r8:00000007  r9:00000034 r10:814000f0 r11:802915a9 cpsr:600c01d6 (MON)
00:00:00   r12:00000000  sp:81441f54  lr:8014bad8  pc:80147ae8 spsr:600c00df (SYS)
00:00:00 PL1 stopped mode:SYS ns:true sp:0x81441f54 lr:0x8014bad8 pc:0x80147ae8 err:exit
00:00:00 PL0 tamago/arm (go1.17.1) • TEE user applet (Secure World)
00:00:00 PL0 obtained 16 random bytes from PL1: 714f378a9733a1a99dcd7faab2fd6936
00:00:00 PL0 requests echo via RPC: hello
00:00:00 PL0 received echo via RPC: hello
...
00:00:05 PL0 is about to read PL1 Secure World memory at 0x90010000
00:00:05    r0:90010000  r1:948220c0  r2:90010000  r3:00000000
00:00:05    r4:00000000  r5:00000000  r6:00000000  r7:00000000
00:00:05    r8:00000007  r9:00000044 r10:948000f0 r11:942cba81 cpsr:600c01d7 (ABT)
00:00:05   r12:00000000  sp:9484ff04  lr:94168868  pc:9401132c spsr:600c00d0 (USR)
00:00:05 PL1 stopped mode:USR ns:false sp:0x9484ff04 lr:0x94168868 pc:0x9401132c err:ABT
00:00:05 PL1 loaded kernel addr:0x80000000 size:3767953 entry:0x8006ce14
00:00:05 PL1 re-launching kernel with TrustZone restrictions
00:00:05 PL1 starting mode:SYS ns:true sp:0x00000000 pc:0x8006ce14
00:00:05 PL1 tamago/arm (go1.17.1) • system/supervisor (Normal World)
00:00:05 PL1 in Normal World is about to perform DCP key derivation
00:00:05    r0:02280000  r1:814403a0  r2:00000001  r3:00000000
00:00:05    r4:00000000  r5:00000000  r6:00000000  r7:00000000
00:00:05    r8:00000007  r9:00000044 r10:814000f0 r11:802915a9 cpsr:200c01d6 (MON)
00:00:05   r12:00000000  sp:81441f38  lr:80147b14  pc:8001132c spsr:200c00df (SYS)
00:00:05 PL1 stopped mode:SYS ns:true sp:0x81441f38 lr:0x80147b14 pc:0x8001132c err:DATA_ABORT
00:00:05 PL1 in Secure World is about to perform DCP key derivation
00:00:05 PL1 in Secure World World successfully used DCP (e777b98dd28a4071a0c94821b7a1a4d1)
00:00:05 PL1 says goodbye

Next

➞ TODO: TEE Client API