The uVisor Documentation
The uVisor documentation consists of two sections, API documentation and core documentation. You can find descriptions of these domains below. If you instead are interested in the general uVisor design philosophy, see the high-level introduction to mbed uVisor or refer to the uVisor GitHub documentation.
These user-facing documents show what you can do with uVisor and how to set up an application that uses the uVisor features.
|I want to…||Document|
|See a high-level introduction to mbed uVisor||Practical real-time operating system security for the masses|
|Start using uVisor in mbed OS on a supported platform||Getting started guide for uVisor on mbed OS|
|Read the uVisor API documentation in detail||The uVisor API documentation|
|Enable the uVisor debug messages||Debugging uVisor on mbed OS|
These documents describe the uVisor internals in more detail. They are useful if you want to contribute to uVisor, compile a specific uVisor version or just know more about the uVisor core.
|I want to…||Document|
|Understand uVisor integration||mbed uVisor integration in mbed OS|
|Port uVisor to my platform/OS||uVisor porting guide for mbed OS|
|Test and experiment with uVisor||Developing with uVisor locally on mbed OS|
|Contribute to uVisor||Contributing to uVisor|
The uVisor is a self-contained software hypervisor that creates independent secure domains on ARM Cortex®-M3 and Cortex®-M4 microcontrollers. It increases resilience against malware and protects secrets from leaking even among different modules of the same application. You can find a high-level overview here (Download PDF).
To start using uVisor, you need to include it as a library in your design. We release the uVisor library periodically into the mbed OS repository, ARMmbed/mbed-os. Review it to learn more about the uVisor security model and see an overview of its features.
You can find most of the uVisor documentation in the docs folder. Please look at the getting started guide for an introduction to uVisor application development. If you are interested in uVisor internals, please refer to the OS-level introduction and the uVisor API docs.
Contributions to this repository in the form of issue reporting and pull requests are welcome! Please read our contribution guidelines first.
Getting started examples
- The basic uVisor example shows how secure interrupts and C++ objects are instantiated in the context of secure boxes.
- The uVisor threaded example demonstrates the configuration of multiple boxes containing secure threads.
- The secure number store example demonstrates secure communication between boxes and implementation of secure APIs. The example shows how a called box can infer the caller’s identity and ownership of secure objects across boots and firmware updates.
Word of caution
This version of the uVisor is an early technology preview with an incomplete implementation of the security features of the final product. Future versions of uVisor will add these functions.
You can find some of the open uVisor issues here:
- Practical real-time operating system security for the masses: ARM TechCon 2016 PDF presentation of the uVisor design philosophy and technical overview.
- mbed uVisor integration in mbed OS in PDF format.
- Q&A with ARM: Securing the IoT using ARM Cortex® processors and a growing mbed platform suite.
The uVisor core supports the following platforms:
- NXP FRDM-K64F.
- STMicroelectronics STM32F429I-DISCO.
- Silicon Labs EFM32 Gecko (Cortex®-M3 and Cortex®-M4 devices).
To use uVisor on a specific OS, you must complete the porting process for that OS. This requires an additional porting step, which the uVisor porting guide for mbed OS documents. uVisor supports the following operating system:
- mbed OS: NXP FRDM-K64F.
The Launchpad GNU ARM Embedded Toolchain builds the uVisor prelinked binary images. Currently, uVisor only supports applications built with this toolchain.
The uVisor design philosophy
The need for security features applies across a wide range of today’s IoT products. Many IoT security problems can be solved with standardized building blocks. The uVisor is one of these basic building blocks – complementary to other important blocks such as robust communication stacks, safe firmware updates and secure crypto libraries.
uVisor provides hardware-enforced compartments (sandboxes) for individual code blocks by limiting access to memories and peripherals using the existing hardware security features of the Cortex®-M microcontrollers.
Breaking the established flat security model of microcontrollers into compartmentalized building blocks results in high security levels because the reach of flaws and external attacks can be limited to less sensitive function blocks.
The uVisor example applications demonstrate features to prevent unauthorized access to flash memory from faulty or compromised code and interrupts. This prevents malware from getting resident on the device and enables protection of device secrets such as cryptographic keys.
Services built on top of the security layer can safely depend on an unclonable trusted identity, secure access to internet services and benefit from encryption key protection.
- Is initialized right after device startup.
- Runs in privileged mode.
- Sets up a protected environment using a Memory Protection Unit (MPU), such as the ARM Cortex®-M MPU or a vendor-specific alternative. In particular:
- Its own memories and the security-critical peripherals are protected from the unprivileged code.
- Access Control Lists (ACLs) limit unprivileged access to selected hardware peripherals and memories.
- Allows interaction from the unprivileged code by exposing SVCall-based APIs.
- Forwards and deprivileges interrupts to the unprivileged handler that has been registered for them.
- Prevents registers leakage when switching execution between privileged and unprivileged code and between mutually untrusted unprivileged modules.
- Forces access to some security-critical peripherals (such as DMA) through SVCall-based APIs.
The unprivileged code
All the code that is not explicitly part of the uVisor is generally referred to as unprivileged code. The unprivileged code:
- Runs in unprivileged mode.
- Has direct memory access to unrestricted unprivileged peripherals.
- Can require exclusive access to memories and peripherals.
- Can register for unprivileged interrupts.
- Cannot access privileged memories and peripherals.
The unprivileged code can be made of mutually untrusted isolated modules (or boxes). This way, even if all are running with unprivileged permissions, different modules can protect their own secrets and execute critical code securely.
For more details about how to setup a secure box and protect memories and peripherals, please read the getting started guide.
Different memory layouts can be used on different platforms, depending on the implemented memory protection scheme and the MPU architecture. The following figure shows the memory layout of a system where the uVisor shares the SRAM module with the operating system (ARMv7-M MPU).
The uVisor secures two main memory blocks, in flash and SRAM respectively. In both cases, it protects its own data and the data of the secure boxes it manages for the unprivileged code. For a more detailed view, please refer to the interactive linker section visualization.
All the unprivileged code that is not protected in a secure domain is referred to as the public box.
This table details the main memory sections that the uVisor protects:
|uVisor code||Unprivileged code can read and execute the uVisor code, so code sharing is facilitated, and privileged-unprivileged transitions are easier.|
|uVisor data / BSS / stack||The uVisor places all its constants, initialized and uninitialized data and the stack in secured areas of memory, separated from the unprivileged code.|
|Secure boxes data / BSS / stack||Through a configuration process, unprivileged code can set up a secure box for which data and stack can be secured by the uVisor and placed in isolated and protected memory areas.|
|Vector table||Interrupt vectors are relocated to the SRAM but protected by the uVisor. Access to them is made through specific APIs.|
The boot process
The uVisor is initialized right after device startup and takes ownership of its most critical assets, such as privileged peripherals, the vector table and memory management. The boot process involves the following steps, in this order:
- Several sanity checks verify integrity of the memory structure as expected by the uVisor.
- The uVisor
bsssection is zeroed, the uVisor
- The uVisor takes ownership of the vector table.
- The virtual Memory Protection Unit (vMPU) is initialized:
- Secure boxes are loaded. For each of them:
bsssection is zeroed.
- Access Control Lists (ACLs) are registered.
- Stacks are initialized.
- A private box context is initialized, if required.
- The MPU (ARM or third-party) is configured.
- Secure boxes are loaded. For each of them:
- Privileged and unprivileged stack pointers are initialized.
- Execution is deprivileged and handed over to the unprivileged code.
From this moment on, the operating system/application runs in unprivileged mode and in the default context, which is that of the public box.
The uVisor can set up a secure execution environment for itself and for each configured secure box. Whenever an event or an explicit call must run in a specific environment, a context switch is triggered.
During a context switch, the uVisor stores the state of the previous context and then:
- Reconfigures the stack pointer and the box context pointer.
- Reconfigures the MPU and the peripherals protection.
- Hands the execution to the target context.
A context switch is triggered automatically every time the target of a function call or exception handling routine (interrupts) belongs to a different secure domain. This applies to user interrupt service routines, threads and direct function calls.