We continue our series of articles on the
centenary of the Great October ... ARM TrustZone.
Today we will understand what Secure World, Normal World, and how two operating systems interact at the program level - trusted (TEE) and guest. We learn what we need and how Secure Monitor works, how interrupts from devices are handled.
If ready - welcome under cat.
')
In the
last article I talked about the hardware implementation. There is everything about the hardware separation of worlds, how to prevent the guest OS from trusted memory and peripherals, and so on. Take from there one thought as a binder:
- Secure / Non-Secure is a processor mode. It is specified by the NS (Non-Secure) bit in the SCR (Secure Configuration Register). If NS = 1, we are in Non-Secure mode, if NS = 0, we are trusted, that is, Secure-mode.
In terms of software implementation, this is all we need. The hardware is located on the other side of the abstraction, watching only NS, and the software is not only executed differently with NS = 0 and 1, but it can also change this bit.
In both modes (NS = 0 and NS = 1), the processor can fully function, so much so that each mode can have its own OS:
- NS = 0: trusted OS or Trusted OS, or Trusted Execution Environment (TEE);
- NS = 1: Guest OS, or Rich OS, or Normal World OS.
Each OS will have its own virtual memory card, its own applications, interrupts, drivers, and so on.
Of course, we don’t always have two operating systems running on ARM. A trusted code may not be a full-fledged OS, but some sort of small security monitor. Or may be completely absent. But in smartphones and tablets, the absence of a trusted OS is a rarity, there are mostly TEE (trusted OS) and a normal OS (for example, Android).
Just don’t take the name TEE, Trusted Execution Environment at face value. If TEE is called trusted, it means that someone trusts its code, because the code leads to the achievement of its goals. Maybe his goal - to destroy the universe, how to know? You do not see the source.
Boot process
Processors always run in secure mode. There are many ARMv7A processors where Security Extensions are disabled. And then they always work as Secure. For example, everyone's favorite Sitara.
But in any case - the processors always start in Secure mode.
The loader participates first in the boot process, and in the case of TrustZone, one of the implementations of the Trusted Boot idea is used - the mechanism that verifies the signature of the image before launching it. The general algorithm here is as follows:
- read the boot image from external media into memory, for example, SD, eMMC, NAND, QSPI;
- check his signature with the public key stitched in the processor during the production phase of the product;
- if the signature is correct, transfer control to the loader.
The public key to verify the signature in the processor is flashed once, and after that only the primary loader, signed by the closed part of this key, can be launched. There is also a field for abuse by major manufacturers.
More information about downloading ARM -
in this article .
Next, the bootloader will check the signature of the trusted OS (TEE) and launch TEE. Ta initializes everything that is needed in TrustZone, leaves Secure mode and transfers control to the guest OS (for example, Linux).
If no TEE is used, and control directly from the bootloader is transferred to Linux, then Linux works in Secure mode. This, however, does not make it a secure OS: there is no barrier between Secure World and Normal World and no trusted OS.
Note that without Trusted Boot, TEE security would be compromised, since it could be replaced by changing the bootloader. What matters is the whole authentication chain provided by binary signatures.
What we want to understand in this article
The picture shows the two operating systems that we just downloaded. The guest OS can call TEE functions, for this it uses Secure Monitor.
In this article, we will understand what the Secure Monitor is, how it is used and how it works.
CPU Modes
In ARMv7A there are quite a few modes of operation. In the picture, they are divided into PL0, PL1, PL2 levels and some of the Secure levels, and some are Non-secure.
PL0 is the unprivileged mode in which normal programs are executed in the OS. Each program is launched with its own memory card configured via the MMU, so it cannot get to other programs like this. But she also cannot climb into the OS, because the OS itself has set it up this way. To access the OS, the application makes a system call (Supervisor Call, SVC command), and the processor jumps to Supervisor mode, PL1.
All the main OS code is executed in Supervisor mode (SVC), at the level of PL1. Here, the OS kernel also has its own MMU table, and the kernel sees memory differently from applications. By the way, the kernel does not have to see all the memory pages of the application, it will be less secure.
Hidden textImagine that some driver will turn on the wrong pointer - the drivers are written by people, so anything is possible. If the entire application memory is visible to the kernel, the driver can spoil some application. If not, it is also bad, but there is a chance that it will fall into the milk and simply cause an exception.
Another important kernel mode is IRQ. Get there when the interrupt is triggered. The IRQ is at the PL1 level, and therefore all the normal Linux device drivers work at the kernel level. The FIQ paired IRQ mode is a quick interrupt. In Linux, it is not used at all, but in the implementation of TrustZone, it has found application - we'll talk about this later.
There are still modes Undef, Abort - these are exceptions when the program is running. If an OS application (or kernel code) tries to execute an invalid command, Undef will occur, and if it turns to prohibited memory, it will be Abort. I already wrote about this in the
last article . In the TrustZone implementation, we can choose whether Abort will be processed in the guest OS (Linux) or redirected to the trusted OS (TEE). In the latter case, we can, for example, record the attempt of the guest OS to get into the area of a trusted OS.
System mode is rarely, if ever, used by everyone.
All of the above modes are in both Secure and Non-Secure modes of operation. In this case, for example, Secure Supervisor and Non-Secure Supervisor are separate modes. They have different MMU tables, different access rights (due to the NS-bit), their data is stored in different cache lines, etc.
It is precisely because of the duplication of the Secure and Non-Secure modes on one core that you can run two OSes.Special processor modes
In the picture above there were a couple of modes:
- Non-Secure Hypervisor (HYP), PL2;
- Secure Monitor (SMC), PL1.
HYP mode is used for hardware virtualization, as in VMWare. It is at the level of PL2, it is even more important than the core of the guest OS and it can allow and forbid everything there, just like TEE. But we are not going to talk about virtualization in this article for two reasons: firstly, there are few ARMv7 processors and software with its support, and secondly, everything from the ARM Virtualization Extensions becomes even more confusing. So it is better to leave virtualization aside.
But we really need the Secure Monitor mode, it is made to switch between Secure and Non-Secure OC. Let's look at it from all sides.
Secure monitor
We have two full-fledged OSes, and they are globally different only in the NS bit:
- Secure OS (TEE), NS = 0;
- Non-Secure OS (guestbook, for example, Linux), NS = 1.
After all, it is logical that the guest OS can not change the NS bit and get the privileges of Secure? Absolutely logical. It is less expected that Secure OS cannot take over and switch to Non-Secure mode by changing NS to 1. But this is also true.
The fact is that switching between modes turned out to be somewhat more difficult than changing one bit:
- To switch between modes, you also need to save / restore context. Almost all the registers for Secure and Non-secure are shared, and they need to be saved and restored.
- In addition, a call from Normal World to Secure World is needed to perform some kind of operation, and the operation usually has parameters and a return value. This also needs to be taken into account.
This is what the Secure Monitor mode came up with. They get there by calling “SMC # 0“, which stands for Secure Monitor call. Moreover, Secure code must call “SMC # 0“ to switch to Non-Secure. And Non-Secure to Secure jumps also.
Hidden text# 0 is just an atavism: at first ARM wanted to transmit the call code through this parameter, but then abandoned the idea and use R0 as the call number.
In general, an SMC call is similar to an operating system (SVC) system call:
- The SVC system call allows an OS application from an unprivileged mode (PL0) to call an OS function (PL1);
- calling the SMC monitor allows the guest OS code (Non-Secure PL1) to call the TEE function (Secure PL1).
The difference is that the return from the system call is not the same as the call itself, but the transition between Secure and Non-Secure is symmetrical, via SMC # 0.
Three features of the Secure Monitor mode allow it to perform the Secure / Non-Secure context switch.
- It has its own stack related to the Secure storage area. The stack is available immediately upon entering Secure Monitor mode, and you can immediately save all the registers (context) of the caller, no matter which one.
- In Secure Monitor mode, we can change the NS bit as we please.
- Changing the NS bit in the Secure Monitor mode, we can see the registers and peripherals from the Secure mode, then from the Non-Secure mode. At the same time, the NS will actually change, and this will affect the operation of the entire hardware binding. However, all this will be in the framework of the implementation of one sequential subprogramme. Because of this, Secure Monitor can prepare everything you need for context switching.
TEE call example
For example, we want TEE to sign a document to us. Data about the document we put in the registers of the processor, for example, like this:
- R0 - opcode: sign the document in memory;
- R1 - the starting address of the document in Normal World memory (remember that the memory representation in Secure and Non-Secure is different);
- R2 is the length of the document;
- R3 is the starting address of the buffer where the signature will fall. We assume that if the buffer is not enough, this is not a TEE problem.
We call SMC # 0 to call TEE. In response, we expect from the TEE the signature in the specified buffer and the result code in the register R0, in order to understand whether the operation was successful or not.
That is, there is a certain exchange protocol between the guest OS and TEE. In ARM, in general, you can behave as you please and come up with any exchange concept, but there is an accepted exchange mechanism described in
ARM SMC calling convention . It describes which registers are used to transmit the command code, data, return values, and so on.
What does Secure Monitor do?
To begin with, the TEE initialization code writes the address of the entry point to the Secure Monitor (subroutine address) in the Monitor mode exception vector table, which is indicated by the MVBAR register.
The MVBAR register is available only in Secure mode and indicates a special table of exception vectors used only when switching to Secure Monitor mode.
ARM also has a regular vector table, which contains entry points for SVC, IRQ, FIQ, and so on. This table is located by default at address 0x00000000, but the address can be configured by VBAR register.
Of course, there are two registers for the operation of two OSs: Secure VBAR and Non-secure VBAR. Which one is available depends on the NS bit.
So, MVBAR is not used for SVC, IRQ, and so on, but only for SMC and a pair of exceptions that can be configured to get into Monitor Mode. For example, we can configure Abort and FIQ to get into Secure Monitor, and due to this, intercept these exceptions.
When initializing, TEE also sets the stack head address for the Secure Monitor, and you're all set, as they say overseas.
An example of the implementation of Secure Monitor can be viewed in the OP-TEE sources, the code is really simple:
https://github.com/OP-TEE/optee_os/blob/master/core/arch/arm/sm/sm_a32.S .
Now let's see what happens when invoking the SMC # 0 command from the guest OS.
Here is a very simplified description of what you will see in the code above. There, operations are not even all performed in the same manner. The goal was to convey a general meaning, nothing more.
In fact, this is almost all that Secure Monitor does — it transfers control to the Secure OS. When Secure OS ends up with a call, it will also call SMC # 0. Secure Monitor will understand by NS = 0 that it is now Secure, and you need to return to Non-Secure, and will do the same commands, but a little vice versa.
If you got to deal with the code, then here's another hint under the spoiler:
Hidden textSecure Monitor identifies the caller's R0 register for the call. There may be two options described in the
ARM SMC calling convention .
- Standard Call - a call that requires the creation of a stream in TEE to process it. For example, accessing the function of a trusted application, or launching in general any TEE function that requires waiting, locks, semaphores, etc.
- Fast Call is a fast TEE call that does not require all of the above. For example, we ask TEE to enable for us a couple more processor cores.
Fast Call is like an interrupt, it will return control quickly enough. Standard Call - like RPC, after its call TEE starts working to its fullest, perform various operations, switch contexts, and maybe wait for the results of the operation.
In principle, Secure Monitor could leave this test to TEE and immediately switch there, but there is such an implementation. It is important not to get lost in this code and see that both calls are executed in the Secure Supervisor mode, and not in the Secure Monitor.
If you are considering OP-TEE code, all calls from Non-Secure World go to Secure for processing, and Secure Monitor does not handle anything. In OP-TEE, it works as a gatekeeper.
UPDATE: Dear
lorc clarifies that in ARM Trusted Firmware, the implementation of Secure Monitor not only switches modes, it also performs a number of system functions, for example, power management. See his comment.
Interrupt and Exception Handling
The guest OS, as a rule, does not really know that it is a guest. Adjusts his memory, interrupts, performs tasks. Everything works as it should, until it hits some kind of constraint imposed on it by TEE. If it hits, Abort will happen, as we wrote in the last article.
In this case, the guest OS will load a bunch of drivers, will assign interrupts and interrupts to these devices will be sent to the guest OS. And why, one wonders, into her? It may well be that the TEE wants to control some devices in monopoly mode and receive its interrupts from them. Now we will understand how two OSes share interruptions.
In the ARM processor, the main interrupt controller is one (let it be GICv2), there are no separate controllers for Secure and Non-Secure.
If an interrupt occurs, GICv2 defaults to delivering it to Secure mode. Then, if an interrupt has occurred, a vector will be loaded from Secure VBAR.
But if we run TEE and Linux in parallel, then we need to somehow divide the interrupts. It doesn’t matter if all interrupts are sent only to TEE (Secure) or Linux (Non-secure).
Therefore, in GICv2, within the framework of Security Extensions support, it was thought up to make an interrupt grouping (GICD_IGROUP register):
- Group 0 is Secure Interrupt, generates IRQ or FIQ, it can be configured;
- Group 1 - Non-Secure interrupt, generates only IRQ.
With this implementation, you can start Linux without any TEE - and then it will be launched in Secure mode by default, set up Secure VBAR for itself, all interrupts will go to it (we wrote about VBAR above). And if Linux is running in guest mode, then TEE will preset all unnecessary interrupts for it to Group 1, and Linux will start in Non-Secure mode. Linux will set up a Non-Secure VBAR for itself, and all its interrupts will go to it.
Idyll and software compatibility , the GIC driver in Linux and should not know, it works in Secure or guest mode.
Well, it would seem, everything is good and clear. If Secure Interrupt occurs, the vector will be loaded from Secure VBAR, otherwise Non-Secure VBAR.
But no! We remember that you cannot simply switch from Secure-mode to Non-Secure, for this we have Secure Monitor.
Therefore:
- if the interruption occurred in Non-Secure mode, and it is Non-Secure, executed as usual, via Non-Secure VBAR;
- if the interruption occurred in Secure mode, and it is Secure, also executed as usual, via Secure VBAR;
- but if the Secure interrupt occurred in Non-Secure mode, then the Secure Monitor is called in order to switch to the Secure mode first;
- about what happens in a pair of Non-Secure-> Secure, you can guess.
The dry residue - Secure-interrupt can occur in Non-Secure mode, and then it will go through the Secure Monitor. The mechanism of its work, described above, now has to figure out whether it is not to interrupt if it was sent, and accordingly handle everything. And there everything in the code for OP-TEE is, look.
A very useful table on this is here:
http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka16352.htmlBut that's not all! In fact, for this to work, there is still something to be done. In the SCR register, which is already familiar to us, there are bits that configure which interrupts and exceptions to be sent to Secure Monitor, and which ones to process through VBAR.
In the picture - SCR from ARM Cortex-A5. The EA, FIQ, and IRQ bits affect routing, respectively, External Abort, and FIQ, and IRQ.
Unfortunately, there is no IRQ Group 0 and IRQ Group 1, and you can only send all IRQs to Secure Monitor, or leave it as it is, via VBAR. As it is - we are not satisfied. Therefore, all developers from filing ARM use this scheme:
- in the GIC, Group 0 is configured for all Secure interrupts;
- for Group 0, FIQ generation is configured, not IRQ;
- in the SCR register, FIQ routing is selected in Secure Monitor, and IRQ is selected via VBAR.
All these settings guest OS will not be able to change. As a result, Secure Interrupt always generates FIQ, and FIQ always enters Secure Monitor from Non-Secure mode.
So, ARMv7 stuff is complicated and sometimes confusing.
In the same way (via the SCR register), you can configure and catch External Abort from Non-Secure mode in Secure Monitor. This can be useful, because External Abort can occur, for example, when trying to access non-secure mode to secure peripherals.
Conclusion
Describe all the programming TrustZone in one review article did not work, and will be continued.
This time we looked at the separation between Secure and Normal World, looked at Secure Monitor, and learned how to catch interrupts in a trusted environment.
The next article will be about TEE: what does it do, how much is it in fact an independent OS, what are the need for trusts, and what is their life cycle.