Main OS execution - usbarmory/GoTEE GitHub Wiki
The execution of a main "rich" operating system, which co-exists with the Trusted OS and Applet covered earlier, happens in the "normal" domain of the secure isolation technology.
In ARM TrustZone the regular OS (or its bootloader) is launched in NonSecure World, also known as Normal World.
To do so we Load the executable just like we did for the Trusted Applet.
os, err := monitor.Load(osEntry, osMemory, false)
[!NOTE] This step does not necessarily need to be performed by the Trusted OS, a first level bootloader could also load the Trusted OS and main OS while booting, without requiring the Trusted OS to do so.
Also note that a bootloader executable could replace the main OS in the example.
The first argument represents the Trusted OS entry point, the second argument is a
DMA region
set with Start
and Size
variables to the physical memory start address and
size that we want to assign to the main OSt, the GoTEE example implements a
basic memory layout.
The boolean argument requests NonSecure execution (TrustZone Normal World), it also automatically assigns system mode to the execution context processor state.
We can invoke String to log the initial Main OS state:
r0:00000000 r1:00000000 r2:00000000 r3:00000000
r4:00000000 r5:00000000 r6:00000000 r7:00000000
r8:00000000 r9:00000000 r10:00000000 r11:00000000 cpsr:00000000 (Unknown)
r12:00000000 sp:00000000 lr:00000000 pc:8406c6c4 spsr:000001df (SYS)
Before execution, we must grant, or deny, access to hardware peripherals according to our security goals.
Let us start with a default "open" configuration, peripheral lockdown is covered in the next tutorial.
First we grant access to ARM CPU co-processors CP10 and CP11, as they are typically required for FPU operation, through the Non-Secure Access Control Register:
// grant access to CP10 and CP11
imx6ul.ARM.NonSecureAccessControl(1<<11 | 1<<10)
While co-processor access can be changed through ARM standard registers, the peripheral access control is SoC specific and therefore will change depending on the specific SoC IPs involved.
On the NXP i.MX6 series peripheral access is mediated by the Central Security Unity (CSU), the TamaGo csu package provides an API for its re-configuration.
By default the CSU sets Secure World exclusive access to all peripherals, we can open it up as follows:
// grant NonSecure access to all peripherals
for i := csu.CSL_MIN; i < csu.CSL_MAX; i++ {
imx6ul.CSU.SetSecurityLevel(i, 0, csu.SEC_LEVEL_0, false)
imx6ul.CSU.SetSecurityLevel(i, 1, csu.SEC_LEVEL_0, false)
}
[!WARNING] Valid TrustZone uses almost certainly require locking down some peripherals, see the next tutorial for a detailed example.
Finally we need to protect Secure World exclusive memory from any access from Normal World, the NXP i.MX6 features the ARM TrustZone Address Space Controller to monitor transactions to external memory.
The TamaGo tzc380 package provides an API for TZASC re-configuration.
By default the TZASC sets Secure World exclusive access to the entire memory (region 0), to allow Normal World access to its assigned memory region the TZASC must be reconfigured as follows:
imx6ul.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.
Once the NonSecure execution context is set, we can Run it:
os.Run()
The main OS can now yield control back to the Trusted OS with a secure monitor call exception (SMC):
PL1 tamago/arm (go1.17.1) • system/supervisor (Normal World)
PL1 in Normal World is about to yield back
PL1 stopped mode:SYS ns:true sp:0x81441f88 lr:0x80146ac8 pc:0x801444c0 err:exit