Memory Access Check of Trusted Firmware-M in Multi-Core Topology
- Author
David Hu
- Organization
Arm Limited
- Contact
Introduction
TF-M memory access check functions tfm_core_has_read_access_to_region()
and
tfm_core_has_write_access_to_region()
check whether an access has proper
permission to read or write the target memory region.
On single Armv8-M core platform, TF-M memory access check implementation relies
on Armv8-M Security Extension (CMSE) intrinsic
cmse_check_address_range()
.
The secure core may not implement CMSE on multi-core platforms. Even if CMSE is
implemented on a multi-core platform, additional check on system-level security
and memory access management units are still necessary since CMSE intrinsics and
TT instructions are only aware of MPU/SAU/IDAU inside the core.
As a result, TF-M in multi-core topology requires a dedicated access check process which can work without CMSE support. This document discuss about the design of the memory access check in multi-core topology.
Overall Design
Memory Access Check Policy
The policies vary in diverse Isolation Levels.
When TF-M works in Isolation Level 1, the access check in multi-core topology checks
Memory region is valid according to system settings
Non-secure client call request should not access secure memory.
Secure services should not directly access non-secure memory. According to PSA Firmware Framework, Secure services should call Secure Partition APIs to ask TF-M SPM to fetch non-secure input/output buffer for them.
Whether read/write attribute match between access and memory region
When TF-M works in Isolation Level 2, the access check in multi-core topology checks:
Memory region is valid according to system settings
Non-secure client call request should not access secure memory.
Secure services should not directly access non-secure memory. According to PSA Firmware Framework, Secure services should call Secure Partition APIs to ask TF-M SPM to fetch non-secure input/outputs buffer for them.
Whether read/write attribute match between access and memory region
Unprivileged secure access should not access privileged secure memory region
The check policy in Isolation Level 3 will be defined according to TF-M future implementation.
The above policies will be adjusted according to TF-M implementation and PSA specs.
General Check Process in TF-M Core
In multi-core topology, tfm_core_has_read_access_to_region()
and
tfm_core_has_write_access_to_region()
are still invoked to keep an uniform
interface to TF-M Core/SPM.
Multi-core topology specific implementations of
tfm_core_has_read_access_to_region()
and
tfm_core_has_write_access_to_region()
are similar to those in single Armv8-M
scenario.
Both functions set corresponding flags according to the parameters and
invoke static function has_access_to_region()
to execute the check process.
Both implementations should be placed in multi-core topology specific files
separated from single Armv8-M access check.
A reference code of tfm_core_has_read_access_to_region()
implementation is
shown below.
int32_t tfm_core_has_read_access_to_region(const void *p, size_t s,
bool ns_caller,
uint32_t privileged)
{
uint8_t flags = MEM_CHECK_MPU_READ;
if (privileged == TFM_PARTITION_UNPRIVILEGED_MODE) {
flags |= MEM_CHECK_MPU_UNPRIV;
}
if (ns_caller) {
flags |= MEM_CHECK_NONSECURE;
}
return has_access_to_region(p, s, flags);
}
A reference code of tfm_core_has_write_access_to_region()
implementation is
shown below.
int32_t tfm_core_has_write_access_to_region(void *p, size_t s,
bool ns_caller,
uint32_t privileged)
{
uint8_t flags = MEM_CHECK_MPU_READWRITE;
if (privileged == TFM_PARTITION_UNPRIVILEGED_MODE) {
flags |= MEM_CHECK_MPU_UNPRIV;
}
if (ns_caller) {
flags |= MEM_CHECK_NONSECURE;
}
return has_access_to_region(p, s, flags);
}
The flags MEM_CHECK_MPU_READ
, MEM_CHECK_MPU_UNPRIV
,
MEM_CHECK_MPU_READWRITE
and MEM_CHECK_NONSECURE
above will be described
in the section Access Permission Flags.
The prototype of static function has_access_to_region()
follows that in
single Armv8-M. The multi-core topology specific has_access_to_region()
executes a general process to check if the access can access the target region.
During the check process, has_access_to_region()
invokes three HAL APIs
tfm_spm_hal_get_mem_security_attr()
, tfm_spm_hal_get_ns_access_attr()
and tfm_spm_hal_get_secure_access_attr()
to retrieve the attributes of
target memory region. has_access_to_region()
compares the access permission
with memory region attributes and determines whether the access is allowed to
access the region according to policy described in Memory Access Check Policy
above.
tfm_spm_hal_get_mem_security_attr()
retrieves the security attributes of
the target memory region.tfm_spm_hal_get_secure_access_attr()
retrieves secure access attributes of
the target memory region.tfm_spm_hal_get_ns_access_attr()
retrieves non-secure access attributes of
the target memory region.The pseudo code of has_access_to_region()
is shown below.
1static int32_t has_access_to_region(const void *p, size_t s, uint8_t flags)
2{
3 struct security_attr_info_t security_attr;
4 struct mem_attr_info_t mem_attr;
5
6 /* The memory access check should be executed inside TF-M PSA RoT */
7 if (not in privileged level) {
8 abort;
9 }
10
11 if (memory region exceeds memory space limitation) {
12 return TFM_ERROR_GENERIC;
13 }
14
15 /* Set initial value */
16 security_attr_init(&security_attr);
17
18 /* Retrieve security attributes of memory region */
19 tfm_spm_hal_get_mem_security_attr(p, s, &security_attr);
20
21 if (!security_attr.is_valid) {
22 /* Invalid memory region */
23 return TFM_ERROR_GENERIC;
24 }
25
26 /* Compare according to current Isolation Level */
27 if (Parameter flags mismatch security attributes) {
28 return TFM_ERROR_GENERIC;
29 }
30
31 /* Set initial value */
32 mem_attr_init(&mem_attr);
33
34 if (security_attr.is_secure) {
35 /* Retrieve access attributes of secure memory region */
36 tfm_spm_hal_get_secure_access_attr(p, s, &mem_attr);
37
38 if (Not in Isolation Level 1) {
39 /* Secure MPU must be enabled in Isolation Level 2 and 3 */
40 if (!mem_attr->is_mpu_enabled) {
41 abort;
42 }
43 }
44 } else {
45 /* Retrieve access attributes of non-secure memory region. */
46 tfm_spm_hal_get_ns_access_attr(p, s, &mem_attr);
47 }
48
49 if (!mem_attr.is_valid) {
50 /* Invalid memory region */
51 return TFM_ERROR_GENERIC;
52 }
53
54 /*
55 * Compare according to current Isolation Level and non-secure/secure
56 * access.
57 */
58 if (access flags matches MPU attributes) {
59 return TFM_SUCCESS;
60 }
61
62 return TFM_ERROR_GENERIC;
63}
Note
It cannot be guaranteed that TF-M provides a comprehensive memory access check on non-secure memory for NSPE client call If non-secure memory protection or isolation is required in a multi-core system, NSPE software should implement and execute the check functionalities in NSPE, rather than relying on TF-M access check.
For example, all the access from NSPE client calls to non-secure memory are classified as unprivileged in current TF-M implementation. Multi-core access check may skip the privileged/unprivileged permission check for non-secure access.
If a multi-core system enforces the privileged/unprivileged isolation and protection of non-secure area, NSPE software should execute the corresponding check functionalities before submitting the NSPE client call request to SPE.
Data Types and APIs
Data Types
Access Permission Flags
The following flags are defined to indicate the access permission attributes. Each flag is mapped to the corresponding CMSE macro. Please refer to ARMv8-M Security Extensions: Requirements on Development Tools for details of each CMSE macro.
MEM_CHECK_MPU_READWRITE
Mapped to CMSE macro CMSE_MPU_READWRITE
to indicate that the access requires
both read and write permission to the target memory region.
#define MEM_CHECK_MPU_READWRITE (1 << 0x0)
MEM_CHECK_MPU_UNPRIV
Mapped to CMSE macro CMSE_MPU_UNPRIV
to indicate that it is an unprivileged
access.
#define MEM_CHECK_MPU_UNPRIV (1 << 0x2)
MEM_CHECK_MPU_READ
Mapped to CMSE macro CMSE_MPU_READ
. It indicates that it is a read-only
access to target memory region.
#define MEM_CHECK_MPU_READ (1 << 0x3)
MEM_CHECK_NONSECURE
Mapped to CSME macro CMSE_NONSECURE
to indicate that it is a access from
non-secure client call request.
If this flag is unset, it indicates the access is required from SPE.
#define MEM_CHECK_AU_NONSECURE (1 << 0x1)
#define MEM_CHECK_MPU_NONSECURE (1 << 0x4)
#define MEM_CHECK_NONSECURE (MEM_CHECK_AU_NONSECURE | \
MEM_CHECK_MPU_NONSECURE)
Security Attributes Information
The structure security_attr_info_t
contains the security attributes
information of the target memory region.
tfm_spm_hal_get_mem_security_attr()
implementation should fill the structure
fields according to the platform specific secure isolation setting.
struct security_attr_info_t {
bool is_valid;
bool is_secure;
};
is_valid
indicates whether the target memory region is valid according to
platform resource assignment and security isolation configurations.is_secure
indicates the target memory region is secure or non-secure. The
value is only valid when is_valid
is true
.Memory Attributes Information
The structure mem_attr_info_t
contains the memory access attributes
information of the target memory region.
tfm_spm_hal_get_secure_access_attr()
and
tfm_spm_hal_get_ns_access_attr()
implementations should fill the structure
fields according to the memory protection settings.
struct mem_attr_info_t {
bool is_mpu_enabled;
bool is_valid;
bool is_xn;
bool is_priv_rd_allow;
bool is_priv_wr_allow;
bool is_unpriv_rd_allow;
bool is_unpriv_wr_allow;
};
is_mpu_enabled
indicates whether the MPU and other management unit are
enabled and work normally.is_valid
indicates whether the target memory region is valid according to
platform resource assignment and memory protection configurations.is_xn
indicates whether the target memory region is Execute Never. This
field is only valid when is_valid
is true
.is_priv_rd_allow
and is_priv_wr_allow
indicates whether the target
memory region allows privileged read/write. Both the fields are valid only
when is_valid
is true
.is_unpriv_rd_allow
and is_unpriv_wr_allow
indicates whether the target
memory region allows unprivileged read/write. Both the fields are valid only
when is_valid
is true
.HAL APIs
tfm_spm_hal_get_mem_security_attr()
tfm_spm_hal_get_mem_security_attr()
retrieves the current active security
configuration information and fills the security_attr_info_t
.
void tfm_spm_hal_get_mem_security_attr(const void *p, size_t s,
struct security_attr_info_t *p_attr);
Paramters |
|
|
Base address of the target memory region |
|
Size of the target memory region |
|
Pointer to the |
Return |
|
|
None |
The implementation should be decoupled from TF-M current isolation level or access check policy.
All the fields in security_attr_info_t
should be explicitly set in
tfm_spm_hal_get_mem_security_attr()
.
If the target memory region crosses boundaries of different security regions or
levels in security isolation configuration,
tfm_spm_hal_get_mem_security_attr()
should determine whether the memory
region violates current security isolation.
It is recommended to mark the target memory region as invalid in such case, even
if the adjoining regions or levels may have the same security configuration.
If the target memory region is not explicitly specified in memory security
configuration, tfm_spm_hal_get_mem_security_attr()
can return the following
values according to actual use case:
Either set
is_valid = false
Or set
is_valid = true
and setis_secure
according to platform specific policy.
tfm_spm_hal_get_secure_access_attr()
tfm_spm_hal_get_secure_access_attr()
retrieves the secure memory protection
configuration information and fills the mem_attr_info_t
.
void tfm_spm_hal_get_secure_access_attr(const void *p, size_t s,
struct mem_attr_info_t *p_attr);
Paramters |
|
|
Base address of the target memory region |
|
Size of the target memory region |
|
Pointer to the |
Return |
|
|
None |
The implementation should be decoupled from TF-M current isolation level or access check policy.
All the fields in mem_attr_info_t
should be explicitly set in
tfm_spm_hal_get_secure_access_attr()
, according to current active memory
protection configuration. It is recommended to retrieve the attributes from
secure MPU and other hardware memory protection unit(s). But the implementation
can be simplified by checking system-level memory region layout setting.
If the target memory region is not specified in current active secure memory
protection configuration, tfm_spm_hal_get_secure_access_attr()
can select
the following values according to actual use case.
Either directly set
is_valid
tofalse
Or set
is_valid
totrue
and set other fields according to other memory assignment information, such as system-level memory region layout.
If secure memory protection unit(s) is disabled and the target memory
region is a valid area according to platform resource assignment,
tfm_spm_hal_get_secure_access_attr()
must set is_mpu_enabled
to
false
and set other fields according to current system-level memory region
layout.
tfm_spm_hal_get_ns_access_attr()
tfm_spm_hal_get_ns_access_attr()
retrieves the non-secure memory protection
configuration information and fills the mem_attr_info_t
.
void tfm_spm_hal_get_ns_access_attr(const void *p, size_t s,
struct mem_attr_info_t *p_attr);
Paramters |
|
|
Base address of the target memory region |
|
Size of the target memory region |
|
Pointer to the |
Return |
|
|
None |
The implementation should be decoupled from TF-M current isolation level or access check policy.
Since non-secure core runs asynchronously, the non-secure MPU setting may be
modified by NSPE OS and the attributes of the target memory region can be
unavailable during tfm_spm_hal_get_ns_access_attr()
execution in TF-M.
When the target memory region is not specified in non-secure MPU,
tfm_spm_hal_get_ns_access_attr()
can set the fields according to other
memory setting information, such as system-level memory region layout.
If non-secure memory protection unit(s) is disabled and the target memory
region is a valid area according to platform resource assignment,
tfm_spm_hal_get_ns_access_attr()
can set the following fields in
mem_attr_info_t
to default values:
is_mpu_enabled = false
is_valid = true
is_xn = true
is_priv_rd_allow = true
is_unpriv_rd_allow = true
is_priv_wr_allow
and is_unpriv_wr_allow
can be set according to current
system-level memory region layout, such as whether it is in code section or data
section.
Copyright (c) 2019-2020, Arm Limited. All rights reserved.