In the cyber security landscape, as you well know, any piece of software may contain various security vulnerabilities (usually, due to its developers' negligence), which may allow a malicious entity to obtain execution privileges on the device.
Though the hacker might gain limited permissions at first (due to the application running as an unprivileged user), a computer bug is never alone in a system and, ultimately, the attacker may be able to circumvent such restrictions by doing (and chaining) additional privilege escalation exploits. The ideal end-game would be to obtain kernel-level execution rights and inject a hidden persistent malware to covertly keep the device under malicious control (e.g., for financial / political gains).
Increased complexity often encountered in modern software is regarded as the primary reason for the apparition of bugs, since having a many number of lines of code makes any verification process considerably harder. Due to this, modern security practices recommend organizing the system's architecture into smaller, standalone components, thus ensuring better security properties and overall good quality of a software (also see: Unix Philosophy).
From here, we can go even further: some parts of a system might be more security-critical than others (cyber-physical interfaces, databases storing sensitive secrets etc.); modern hardware might allow us to ensure additional protections for these such that, even when the Operating System kernel gets breached, an attacker's access remains confined by the processor and it won't be able to cause system-wide mayhem. Enter Trusted Execution Environments!
A Trusted Execution Environment (TEE) is an isolated execution context whose internal state (memory, CPU registers etc.) is secured and cannot be reached by normal software. This means untrusted applications, and even the highly-privileged Operating System (often considered as being vulnerable to cyber attacks due to its huge complexity) cannot read / write a TEE's protected zones and cannot access secure peripherals (e.g., keypads for sensitive input, protected screens).
Trusted Execution Environments are usually implemented by a combined hardware + software approach: the CPU architecture must be extended to discern normal vs. secure execution and deny requests to secure memory addresses, while some kind of trusted firmware is used to retain an appropriate level of flexibility for a highly secure solution (for increased compatibility with various hardware / peripherals, enforce application-specific rules, implement platform upgradeability - especially in the case of bug disclosures).
Note that a TEE subsystem should follow the minimal complexity philosophy mentioned above. This means that the developers must design their application using a modular architecture and partition the code into trusted (secure) and untrusted counterparts. The trusted application must the only one with access to manipulate sensitive data (e.g., passwords / encryption keys, confidential information, cyber-physical commands etc.); the untrusted components (including the traditional / feature-rich Operating System) will only see such information travel in encrypted & integrity-authenticated (protected from modifications) form and will only serve as an intermmediary for the TAs to communicate with external entities (e.g., human-machine interface peripherals, networks, persistent storage) – practically, it should do anything that does not need to directly process / understand any of it.
Some of the commercially available TEE technologies are:
On a final note for this section: unfortunately, consumer-friendly TEE solutions for personal computing (i.e., desktops & laptops) remain the domain of ongoing research and no such technologies are yet available on mainstream (PC) platforms.
Modern ARM CPU architectures feature the TrustZone Security Extensions (starting with armv7 for ARM Cortex-A, expanding to Cortex-Ms after v8), which introduces the necessary execution context separation required for implementing TEEs, adding a new Secure World and keeping the previous domain as Non-Secure World.
It realizes this by using a new CPU state bit (NS - Non-Secure - flag) create an additional privilege level - orthogonal to the original Exception Levels (EL 0-2), meaning both Worlds can have software running in each of these common ELs. An additional Exception Level, EL3 (Firmware / Secure Monitor), was also introduced to securely manage the software transition between the two trust domains.
Furthermore, the ARM AXI (Advanced eXtensible Interface) bus protocol was also extended to tag all memory transactions with a NS flag, which the on-chip modules (e.g., SRAM / DRAM) and other peripherals can check against and allow / deny access to specific resources.
Devices using this kind of access control are called TrustZone-aware peripherals. For example, interfaces used for connecting fingerprint readers and NFCs used for banking applications could be configured to block access from untrusted applications (modern iPhones smartphones already do this!).
We highlight, in particular, ARM's official TZASC (TrustZone Address Space Controller) module, a SoC (System-on-Chip)-integrated peripheral which filters all unauthorized requests to the secure memory regions (e.g., Secure SRAM / TZDRAM) mapped the physical address space of a chip. The TZASC gets configured early in the boot process by the ARM Trusted Firmware (executing as BL31) before loading any Normal World components.
Note that the two domains (Secure & Non-Secure Worlds) can communicate to each other via messages (using a buffer in unprotected shared memory) relayed by the Secure Monitor (solely running at the most privileged level: EL3, provided by ARM Trusted Firmware-A for our boards - as we will see at the lab) when the special SMC
instruction is invoked (very similar to the traditional userspace to kernel system call mechanism).
This may the case either when a normal application wishes to request secure services from its Trusted counterpart (e.g., request to have a piece of data decrypted on behalf of an authorized user) or the reverse: a TA requests a untrusted function typically implemented by the Operating System (filesystem / network / untrusted hardware access - almost anything for which the userspace software or drivers are really complex and should not incur additional overhead to the secure components).
Finally, we mention that Trusted Execution Environments have many interesting problems to overcome: context switching, interrupt handling, trusted application scheduling & synchronization, function parameter marshalling and validation, code partitioning etc. For our purposes, however, we will simply ignore them and proceed with an already finished implementation, OP-TEE!
OP-TEE (Open Trusted Execution Environment) is a open-source project which aims to implement a complete Secure Operating System to run inside ARM TrustZone-enabled Secure World and facilitate the development of Trusted Applications (TAs).
OP-TEE's main components are:
The OP-TEE kernel resides in the ARM TrustZone Secure World's Exception Level 1 (same place as the Non-Secure Kernel, but different trust domain) and gets loaded early in the boot process (BL32), just before the normal bootloader + Linux OS. The Trusted OS provides a minimal set of services (e.g., cryptographic routines, secure memory management) and drivers (for interfacing with the secure hardware) for the Trusted Applications, as well as a mechanism for calling untrusted functions from the Rich OS (i.e., Linux) in order to limit its complexity as per the aforementioned security recommendations.
Note that BL31 (the ARM Trusted Firmware-A, in most cases) is reponsible for initializing the TrustZone hardware isolation (especially the TZASC memory regions, interrupt controller etc.). The Secure Monitor (EL3) implementation is also part of TF-A and NOT OP-TEE, though it has a module specific for integration with the latter.
OP-TEE also provides a software framework for developing Trusted Applications. Recall that such applications have two parts: an untrusted Host Application running inside Normal World (under the Linux kernel) and the proper TA executing inside the isolated context. The SDK covers both components and provides an API (as C functions) for facilitating the communication between them (which we'll briefly use a the lab).
Again, we point to the fact that a Secure Kernel does not implement several features which, nowadays, we take for granted, such as: networking (TCP/IP stack), file system access (and, in fact, most kinds of external disk I/O), graphical devices (i.e., GPUs), multi-threading etc. – since all of those sum up to millions of lines of code, defeating the assumptions of the secure architecture for being its components being small and easy to verify for correctness. Nevertheless, we can import and readily use these functions from the Normal OS via proxying (system calls are forwarded by the Secure Monitor through the Worlds barrier, executed on the untrusted realm and the results transported back to the secure application). A word of warning, though: such functionality must be regarded as insecure and all data must be protected (encrypted) and validated (authenticated) beforehand.