Skip to content

TrustZone configuration

Andrea Barisani edited this page Oct 8, 2024 · 29 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(osEntry, osMemory, 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?).

Warning

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.

Note

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 tzc380 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.

When a Secure World default for region 0 is not desirable, due to TZASC region size and offset limitations), the configuration can be inverted. The GoTEE example does so to grant more than 256MB to NonSecure World memory (assigned to region 0) and consequently explicitly setting Secure World and Trusted Applets memory (region 1 and 2).

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 a basic memory layout.

 dma.Init(SecureDMAStart, SecureDMASize)

Next

Examples