blob: 3bd1055829c1d75ac19f68e7197c128cc3359b7c [file] [log] [blame]
.. SPDX-License-Identifier: BSD-3-Clause
.. SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
###############
EL0 apps in RMM
###############
****
Goal
****
The goal of introducing EL0 app support in TF-RMM is to improve security by
minimizing the amount of code running in privileged mode. This is achieved by
deprivileging parts of RMM to run at EL0 using the Virtualization Host
Extensions (VHE). This approach is especially desirable for code pulled in from
external projects (like Mbed TLS), as such components are often complex and
sensitive. RMM apps now run at EL0 using the EL2&0 translation regime, with the
CPU configured such that an eret instruction in EL2 transitions directly to EL0,
and the target EL of an SVC instruction is EL2. Additionally, each EL0 app has
its own isolated address space, which not only improves fault isolation but also
helps mitigate speculation-based side channels, allowing secrets within apps to
be better protected from speculative execution. This isolation mechanism also
lays the groundwork for future side-channel mitigations and facilitates their
deployment. Overall, this scheme is especially useful for sand-boxing complex
and sensitive code at EL0 and increasing the robustness of RMM.
|RMM app EL0|
This design document covers the various aspects of the implementation of the EL0
app support in RMM.
*********************
Building app binaries
*********************
The RMM app's code is in the ``app`` directory, and they may link to
libraries from the ``lib`` directory. RMM apps use a common linker script that
contains a few predefined section. The RMM app cmake file first compiles and
links the RMM app elf binary. Then the python script ``app/gen_app_bin.py``
is called that extracts the section contents along with address and size
information. Then this information is used to generate a bin file. The bin file
is a concatenation of the following:
+----------------+-------------------------------------+
| 1 page size of | 8 bytes padding (explained later) |
| header +-------------------------------------+
| | magic |
| +-------------------------------------+
| | header version |
| +-------------------------------------+
| | app_name |
| +-------------------------------------+
| | app id |
| +-------------------------------------+
| | app_len |
| +-------------------------------------+
| | section 1 offset |
| +-------------------------------------+
| | section 1 size |
| +-------------------------------------+
| | ... |
| +-------------------------------------+
| | section n offset |
| +-------------------------------------+
| | section n size |
| +-------------------------------------+
| | magic |
| +-------------------------------------+
| | Padding |
+----------------+-------------------------------------+
| content of section 1 |
+------------------------------------------------------+
| ... |
+------------------------------------------------------+
| content of section n |
+------------------------------------------------------+
The number and order of sections in an RMM app elf/binary is hardcoded in the
linker script, the python script that does the extraction of section content,
and in the header definition.
The RMM app bin size is padded to be a multiple of page size.
************************
Building final RMM image
************************
The RMM core code is built and linked using its own linker script. Then objcopy
is called on the resulting elf to extract the sections to an img file. Then the
bin files for the RMM apps are built as described above. The final RMM image
is created concatenating the following elements by the python script
``app/bundle_app_rmm.py``:
* The first RMM app bin. The first 8 byte padding of the header is updated to a
``BL`` (branch with link) instruction, followed by 4 bytes of padding. This
allows the offset of the first meaningful field - the section offsets in the
app header - to remain consistent with its position in the original app
binary. The ``BL`` instruction branches to the start of the text section in
the RMM core img. The ``BL`` instruction uses ``PC`` relative offset to encode
the branch target, so the instruction can be calculated when the bundled image
is assembled. The return value that is saved in the Link Register by the
``BL`` instruction is used by RMM core to determine the memory address of the
header of the first app in the bundle.
* The rest of the RMM app bins (if any) unmodified.
* The RMM core img file aligned to 64KB offset to meet ELF loader requirements.
The packaged RMM binary must be loaded at a 64KB-aligned physical address to
ensure that the RMM core itself is also 64KB-aligned. This alignment requirement
stems from the use of a 1:1 virtual-to-physical address mapping for the RMM
core. In contrast, EL0 apps run at virtual addresses determined at compile time,
which are already 64KB-aligned. As a result, the physical address alignment of
app binaries is not significant.
The number of RMM apps expected by RMM core is hardcoded in RMM core.
The RMM app headers are parsed by the RMM core during the coldboot phase. The
RMM app headers contain a magic value, so if less than the expected number of
RMM apps is added to the final bin that can be detected, then boot fails in that
case. If more than the hardcoded number of RMM apps is present, RMM boot fails
as well.
|RMM bin generation|
****************
RMM App Instance
****************
In this document app instance refers to an execution context, along with the
necessary memory that is allocated for it. In operating system terms, a thread
is analogous to an EL0 App instance.
To manage and run an RMM app instance, RMM core needs memory for the following
purposes:
1. RMM app context: A single page of memory for use by the context switch logic
to back up registers. This memory page acts as app's CPU context storage when
it is not scheduled. This page is mapped in the APP VA space as well, with
permissions that it is only accessible by privileged execution (i.e. EL2).
The mapping is necessary because, on exit from an RMM app to the RMM core,
the RMM app's virtual address (VA) space is still active and set up in the
High VA region, while the RMM core's VA space is not yet available. To switch
back to RMM Core High VA space, the general purpose registers need to be
backed up beforehand. To enable this, the app context page is mapped at the
beginning of the apps VA space (without UNPRIV access). This fixed mapping
allows the context switch code to use a hardcoded address for the register
backup. Consequently, the EL0 apps accessible address space begins at a 4KB
offset from the start of the High VA region
2. A memory page that is used to exchange RMM app specific data between the
RMM app and the RMM app caller mapped both in EL2 and in EL0. (Also
referenced as shared page by this document.)
3. Metadata for the RMM app (``struct app_data_cfg``). This includes
* xlat library structures including the pointer to the pagetable for the RMM
app.
* entry point
* PA of the app_reg_ctx
* PA of the shared page
4. Page containing the pagetable for the EL0 memory mappings (currently the RMM
app has a limited 2MB VA space)
5. Stack for RMM app.
6. Heap for RMM app.
In the current implementation there are 3 possible locations where the above
mentioned data is stored:
a. In statically allocated per-cpu buffers.
b. In the rec structure for a REC and in the rec aux pages
c. In the pdev structure for a PDEV object in the pdev aux pages
In case of *b.* and *c.* the memory pages are delegated by the host.
Note that this introduces a limitation on the stack/heap of the RMM apps, as the
max count of aux pages is limited, and RMM core is already using some of them.
****************
RMM App VA space
****************
The RMM app va space (as it is configured in the xlat library) contains the
following regions:
* Single page for the RMM app context (Transient)
* RMM app text
* RMM app RO data
* RMM app data
* RMM app BSS
* RMM app Shared (Transient)
* RMM app heap (Transient)
* RMM app stack (Transient)
The pages that are mapped in the transient region are private to the RMM app
instance. As all the other regions are common, it is possible to initialise the
xlat_ctx_cfg used by apps during RMM boot.
**************************
Initialising RMM app pages
**************************
The pages that are instance specific (`RMM App Instance`_) needs to be initialised
on creating an RMM app instance.
As it is possible that the pages are delegated by the host, and the pages need
to be mapped dynamically using slot-buffer framework, a new slot is added to the
slot buffer. When initialising such pages, the page under initialisation is
mapped into this new slot, and unmapped after initialising.
The ``app_init_data`` function receives an array of physical addresses of
the pages to be used for the RMM app. In case of initialisation the code checks
whether the PA is in the range of the RMM core's rw PA range. If so, it can
write to it directly. If not, the slot buffer mechanism is used as described
above.
Mappings when using AUX pages for RMM app RW memory:
|RMM app memory aux|
Mappings when using statically allocated pages for RMM app RW memory:
|RMM app memory static|
*********************
RMM App Service Calls
*********************
Since the apps are running at EL0, it cannot perform some privileged operations
like communicate with EL3 or copy data from NS host buffer etc. RMM provides
some services for RMM apps (like printing). The services are required to have
the signature
.. code-block:: bash
typedef uint64_t (*app_service_func)(struct app_data_cfg *app_data,
unsigned long arg0,
unsigned long arg1,
unsigned long arg2,
unsigned long arg3);
Services are expected to be thread safe.
Some services can call other apps to perform its functionality (like PRNG). But
this kind of nesting is limited to one other app to avoid interdependencies
between apps.
Services are stored in a function pointer array. There is a single array in the
system.
The RMM app that calls a service must do an SVC with a predefined immediate
value. The index of the service to be executed is selected by the value of the
X0 register.
The el0_app framework calls app_enter in a loop. It exits the loop
if the SVC immediate value reports function return or yield (in future).
********************
Debugging RMM on FVP
********************
The RMM core text section offset is changed due to the app binaries being
prepended before the RMM core img. The ``bundle_app_rmm.py`` script prints the
text section offset in the binary which should be added to the RMM load address
when loading the elf in the debugger for symbols. The script output is in the
file ``<build_dir>/<build_type>/bundle_app_out.txt``. The offset is also printed
out during the build process.
The RMM apps are linked to the VA address that they are going to be loaded by
RMM core, so the RMM app symbols needs to be loaded in the debugger with offset
``0x0``
*******************
RMM Fake Host Build
*******************
In case of the fake host build, the applications are compiled as a standalone
elf file. The RMM core is compiled to the elf file ``rmm_core.elf``. The path
to the RMM app elf files are passed to ``rmm_core.elf`` as a command line
parameter, along with the ID of the RMM apps. The first time the main RMM
process calls ``app_init_data`` the process is forked, the image of the
requested RMM app is loaded in the new process, and a named-pipe connection is
established between the two processes. When the RMM app process is created, for
each ``app_init_data`` (including the first call) a new thread is created. The
main thread in the RMM app process is responsible for dispatching the RMM app
calls and returns between the main RMM process and the RMM app thread.
There is no shared memory between the main and the RMM app processes, memory
sharing is emulated by sending over the content of the main process's "shared
page" to the RMM app thread on an ``app_run`` call, and sending back the content
of the RMM app thread's "shared page" on RMM app function return.
|RMM app host|
The help of the main process:
.. code-block::
$ rmm_core.elf --help
Run RMM on the host
Usage: rmm_core.elf [-h|--help] [app_id app_elf [...]]
Arguments:
-h, --help print this message and exit.
app_id Integer value of app id of the app.
app_elf path to the app's elf file.
An example call:
.. code-block:: bash
$ Debug/rmm_core.elf \
103 Debug/rmm_app_random.elf \
211 Debug/rmm_app_attestation.elf
*************************
RMM Apps currently in RMM
*************************
Currently there are 2 RMM apps in RMM:
- attestation: Used for doing attestation related tasks, like attestation
token generation and hash calculation
- random: A small application with a pseudo random number generator, either
seeded with a true random sequence, or with input from an already initialised
random RMM app.
Currently both apps have their own build of Mbed TLS linked to them.
There is an instance of random number RMM app initialised for each of the CPUs.
There is also an attestation app initialised for each CPU for doing RIM
extension, and there is an RMM app instance initialised for each REC, to do
measurement extend and attestation token generation.
The attestation RMM app uses the random RMM app via a service. The attestation
RMM app reaches the EL3 interface (for requesting realm attestation key
and platform token) via an RMM app service.
The layout of the RMM apps can be seen on the image below:
|RMM app layout|
Note: To be continued with DA related RMM apps
************************
Directory layout of apps
************************
.. code-block:: bash
.
└── app
├── attestation
├── el0_app
├── mbedtls_attest # Library (EL0)
├── qcbor # Library (EL0)
├── src # Implementation of the attestation RMM app (EL0)
└── t_cose # Library (EL0)
└── rmm_stub # Helper function for using the Attestation RMM app (EL2)
├── common
├── el0_app # Common code that is used by multiple apps (EL0)
├── framework # Framework for calling an app and provide app services (EL2)
├── include
└── rmm_svc # Implementation of RMM services provided to apps (EL2)
└── random
├── el0_app
├── mbedtls_random # Library (EL0)
└── src # Implementation of the Random RMM app (EL0)
└── rmm_stub # Helper function for using the Random RMM app (EL2)
**********************************************
Proposed Enhancements to the RMM App Framework
**********************************************
#. Shared Memory for ``fake_host``
Implement support for shared memory to enable communication between processes
within the ``fake_host`` environment.
#. Configurable App ID
Currently, the App ID is hardcoded. There is a suggestion to derive it from a
header generated as part of the configuration. This approach can be explored
further.
#. App BSS Allocation
Consider whether the App BSS should be allocated dynamically. Alternatively,
RMM could reserve a dedicated section for app BSS and allocate memory from
there.
#. Common Binary Generation Logic
The logic for generating app bin files can be consolidated or reused.
#. Avoid Unnecessary Remapping of Shared Buffers
When shared buffers are located in auxiliary granules, remapping may not be
needed if the granules are already mapped.
#. Unify VBAR Usage
Currently, separate VBARs are set up for the app and RMM. Evaluate whether a
single VBAR could be used to simplify the design.
#. Lightweight App Threading in ``fake_host``
Replace full PThreads with a lighter-weight threading model for running apps
in the ``fake_host`` test environment.
#. Enable FEAT_PAN/PAN3 for EL0
Ensure that the appropriate Privileged Access Never (PAN) features (including
PAN3) are correctly set when executing code at EL0.
#. Hardening ``app_services``
The ``app_services`` interface should be hardened. This includes:
- Clear documentation of function arguments and return codes
- Proper validation of input parameters
#. Harden ``rmm_stub`` and Define Conventions
The ``rmm_stub`` must validate all return values from EL0 apps. Also, define
a consistent convention for parameter passing between the EL0 app and the
stub to support future extensibility.
#. Optional TLBI on EL0 Entry
In certain cases, Translation Lookaside Buffer Invalidate (TLBI) may not be
needed when entering an EL0 app. This optimization is already noted in the
code and should be validated and documented.
#. Post-Build App Repackaging
Explore the possibility of repackaging EL0 apps after the main RMM binary is
built.
#. Unmap heap pages allocated to app from RMM high VA space.
When heap is allocated from REC aux granules, the aux granules are always
mapped when the REC is in use and the app is not running. Even if app is
running stack and pages table page not needed to be mapped, and unless some
data is being shared between core and app via heap, heap pages are not needed
to be mapped
.. |RMM app EL0| image:: ./diagrams/rmm_app_EL0.drawio.png
.. |RMM bin generation| image:: ./diagrams/rmm_bin_generation.drawio.png
.. |RMM app memory aux| image:: ./diagrams/rmm_app_memory_aux.drawio.png
.. |RMM app memory static| image:: ./diagrams/rmm_app_memory_static.drawio.png
.. |RMM app host| image:: ./diagrams/rmm_app_on_host.drawio.png
.. |RMM app layout| image:: ./diagrams/rmm_app_layout.drawio.png