Core: Sanitize iovec parameters in TF-M Core
Do a sanity check on the iovec parameters of secure function calls in
TF-M core.
Change-Id: I57f411c6fbc116df3c09869d8540a0c27cce6918
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
diff --git a/interface/include/tfm_api.h b/interface/include/tfm_api.h
index 5194c2a..8dbf57c 100644
--- a/interface/include/tfm_api.h
+++ b/interface/include/tfm_api.h
@@ -36,6 +36,9 @@
*/
#define TFM_CLIENT_ID_IS_NS(client_id) ((client_id)<0)
+/* Maximum number of input and output vectors */
+#define PSA_MAX_IOVEC (4)
+
/* FixMe: sort out DEBUG compile option and limit return value options
* on external interfaces */
/* Note:
diff --git a/secure_fw/core/tfm_secure_api.c b/secure_fw/core/tfm_secure_api.c
index cf19b0c..4eac9f9 100644
--- a/secure_fw/core/tfm_secure_api.c
+++ b/secure_fw/core/tfm_secure_api.c
@@ -8,6 +8,7 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
+#include <arm_cmse.h>
#include "cmsis.h"
#include "tfm_secure_api.h"
#include "tfm_nspm.h"
@@ -23,12 +24,15 @@
#error TFM_LVL is not defined!
#endif
-#if TFM_LVL == 1
/* Macros to pick linker symbols and allow references to sections */
#define REGION(a, b, c) a##b##c
#define REGION_NAME(a, b, c) REGION(a, b, c)
#define REGION_DECLARE(a, b, c) extern uint32_t REGION_NAME(a, b, c)
+REGION_DECLARE(Image$$, TFM_UNPRIV_SCRATCH, $$ZI$$Base);
+REGION_DECLARE(Image$$, TFM_UNPRIV_SCRATCH, $$ZI$$Limit);
+
+#if TFM_LVL == 1
REGION_DECLARE(Image$$, TFM_SECURE_STACK, $$ZI$$Base);
REGION_DECLARE(Image$$, TFM_SECURE_STACK, $$ZI$$Limit);
#endif
@@ -76,6 +80,208 @@
return;
}
+/**
+ * \brief Check whether a memory range is inside a memory region.
+ *
+ * \param[in] p The start address of the range to check
+ * \param[in] s The size of the range to check
+ * \param[in] region_start The start address of the region, which should
+ * contain the range
+ * \param[in] region_len The size of the region, which should contain the
+ * range
+ *
+ * \return 1 if the region contains the range, 0 otherwise.
+ */
+static int32_t check_address_range(const void *p, size_t s,
+ uintptr_t region_start, uint32_t region_len)
+{
+ int32_t range_in_region = 0;
+
+ /* Check for overflow in the range parameters */
+ if ((uintptr_t)p > UINTPTR_MAX-s) {
+ return 0;
+ }
+
+ /* We trust the region parameters, and don't check for overflow */
+
+ /* Calculate the result */
+ range_in_region = ((uintptr_t)p >= region_start) &&
+ ((uintptr_t)p+s <= region_start+region_len);
+
+ return range_in_region;
+}
+
+/**
+ * \brief Check whether the current partition has access to a memory range
+ *
+ * This function assumes, that the current MPU configuration is set for the
+ * partition to be checked. The flags should contain information of the
+ * execution mode of the partition code (priv/unpriv), and access type
+ * (read/write) as specified in "ARMv8-M Security Extensions: Requirements on
+ * Development Tools" chapter "Address range check intrinsic"
+ *
+ * \param[in] p The start address of the range to check
+ * \param[in] s The size of the range to check
+ * \param[in] flags The flags to pass to the cmse_check_address_range func
+ *
+ * \return 1 if the partition has access to the memory range, 0 otherwise.
+ */
+static int32_t has_access_to_region(const void *p, size_t s, uint32_t flags)
+{
+ int32_t range_access_allowed_by_mpu;
+
+ uint32_t scratch_base =
+ (uint32_t)®ION_NAME(Image$$, TFM_UNPRIV_SCRATCH, $$ZI$$Base);
+ uint32_t scratch_limit =
+ (uint32_t)®ION_NAME(Image$$, TFM_UNPRIV_SCRATCH, $$ZI$$Limit);
+
+ /* Use the TT instruction to check access to the partition's regions*/
+ range_access_allowed_by_mpu =
+ cmse_check_address_range((void *)p, s, flags) != NULL;
+
+ if (range_access_allowed_by_mpu) {
+ return 1;
+ }
+
+ /* If the check for the current MPU settings fails, check for the share
+ * region, only if the partition is secure
+ */
+ if ((flags & CMSE_NONSECURE) == 0) {
+ if (check_address_range(p, s, scratch_base,
+ scratch_limit+1-scratch_base)) {
+ return 1;
+ }
+ }
+
+ /* If all else fails, check whether the region is in the non-secure
+ * memory
+ */
+ return
+ check_address_range(p, s, NS_CODE_START, NS_CODE_LIMIT+1-NS_CODE_START) ||
+ check_address_range(p, s, NS_DATA_START, NS_DATA_LIMIT+1-NS_DATA_START);
+}
+
+/**
+ * \brief Check whether the current partition has read access to a memory range
+ *
+ * This function assumes, that the current MPU configuration is set for the
+ * partition to be checked.
+ *
+ * \param[in] p The start address of the range to check
+ * \param[in] s The size of the range to check
+ * \param[in] ns_caller Whether the current partition is a non-secure one
+ *
+ * \return 1 if the partition has access to the memory range, 0 otherwise.
+ */
+static int32_t has_read_access_to_region(const void *p, size_t s,
+ int32_t ns_caller)
+{
+ uint32_t flags = CMSE_MPU_UNPRIV|CMSE_MPU_READ;
+
+ if (ns_caller) {
+ flags |= CMSE_NONSECURE;
+ }
+
+ return has_access_to_region(p, s, flags);
+}
+
+/**
+ * \brief Check whether the current partition has write access to a memory range
+ *
+ * This function assumes, that the current MPU configuration is set for the
+ * partition to be checked.
+ *
+ * \param[in] p The start address of the range to check
+ * \param[in] s The size of the range to check
+ * \param[in] ns_caller Whether the current partition is a non-secure one
+ *
+ * \return 1 if the partition has access to the memory range, 0 otherwise.
+ */
+static int32_t has_write_access_to_region(void *p, size_t s, int32_t ns_caller)
+{
+ uint32_t flags = CMSE_MPU_UNPRIV|CMSE_MPU_READWRITE;
+
+ if (ns_caller) {
+ flags |= CMSE_NONSECURE;
+ }
+
+ return has_access_to_region(p, s, flags);
+}
+
+/** \brief Check whether the iovec parameters are valid, and the memory ranges
+ * are in the posession of the calling partition
+ *
+ * \param[in] desc_ptr The secure function request descriptor
+ *
+ * \return Return /ref TFM_SUCCESS if the iovec parameters are valid, error code
+ * otherwise as in /ref tfm_status_e
+ */
+static int32_t tfm_core_check_sfn_parameters(struct tfm_sfn_req_s *desc_ptr)
+{
+ struct psa_invec *in_vec = (psa_invec *)desc_ptr->args[0];
+ size_t in_len = desc_ptr->args[1];
+ struct psa_outvec *out_vec = (psa_outvec *)desc_ptr->args[2];
+ size_t out_len = desc_ptr->args[3];
+
+ uint32_t i;
+
+ /* The number of vectors are within range. Extra checks to avoid overflow */
+ if ((in_len > PSA_MAX_IOVEC) || (out_len > PSA_MAX_IOVEC) ||
+ (in_len + out_len > PSA_MAX_IOVEC)) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+
+ /* Check whether the caller partition has at write access to the iovec
+ * structures themselves. Use the TT instruction for this.
+ */
+ if (in_len > 0) {
+ if ((in_vec == NULL) ||
+ (has_write_access_to_region(in_vec, sizeof(psa_invec)*in_len,
+ desc_ptr->ns_caller) != 1)) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+ } else {
+ if (in_vec != NULL) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+ }
+ if (out_len > 0) {
+ if ((out_vec == NULL) ||
+ (has_write_access_to_region(out_vec, sizeof(psa_outvec)*out_len,
+ desc_ptr->ns_caller) != 1)) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+ } else {
+ if (out_vec != NULL) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+ }
+
+ /* Check whether the caller partition has access to the data inside the
+ * iovecs
+ */
+ for (i = 0; i < in_len; ++i) {
+ if (in_vec[i].len > 0) {
+ if ((in_vec[i].base == NULL) ||
+ (has_read_access_to_region(in_vec[i].base, in_vec[i].len,
+ desc_ptr->ns_caller) != 1)) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+ }
+ }
+ for (i = 0; i < out_len; ++i) {
+ if (out_vec[i].len > 0) {
+ if ((out_vec[i].base == NULL) ||
+ (has_write_access_to_region(out_vec[i].base, out_vec[i].len,
+ desc_ptr->ns_caller) != 1)) {
+ return TFM_ERROR_STATUS(TFM_ERROR_INVALID_PARAMETER);
+ }
+ }
+ }
+
+ return TFM_SUCCESS;
+}
+
static int32_t tfm_start_partition(struct tfm_sfn_req_s *desc_ptr,
uint32_t excReturn)
{
@@ -400,8 +606,18 @@
}
__disable_irq();
+
desc_ptr->caller_part_idx = tfm_spm_partition_get_running_partition_idx();
+ if (desc_ptr->iovec_api == TFM_SFN_API_IOVEC) {
+ res = tfm_core_check_sfn_parameters(desc_ptr);
+ if (res != TFM_SUCCESS) {
+ /* The sanity check of iovecs failed. */
+ __enable_irq();
+ return TFM_ERROR_STATUS(res);
+ }
+ }
+
res = tfm_core_check_sfn_req_rules(desc_ptr);
if (res != TFM_SUCCESS) {
/* FixMe: error compartmentalization TBD */
@@ -432,6 +648,14 @@
int32_t *args;
int32_t retVal;
+ if (desc_ptr->iovec_api == TFM_SFN_API_IOVEC) {
+ res = tfm_core_check_sfn_parameters(desc_ptr);
+ if (res != TFM_SUCCESS) {
+ /* The sanity check of iovecs failed. */
+ return res;
+ }
+ }
+
/* No excReturn value is needed as no exception handling is used */
res = tfm_core_sfn_request_handler(desc_ptr, 0);
@@ -689,6 +913,8 @@
uint32_t tfm_core_partition_request_svc_handler(
struct tfm_exc_stack_t *svc_ctx, uint32_t excReturn)
{
+ struct tfm_sfn_req_s *desc_ptr;
+
if (!(excReturn & EXC_RETURN_STACK_PROCESS)) {
/* Service request SVC called with MSP active.
* Either invalid configuration for Thread mode or SVC called
@@ -699,7 +925,7 @@
tfm_secure_api_error_handler();
}
- struct tfm_sfn_req_s *desc_ptr = (struct tfm_sfn_req_s *)svc_ctx->R0;
+ desc_ptr = (struct tfm_sfn_req_s *)svc_ctx->R0;
if (tfm_core_sfn_request_handler(desc_ptr, excReturn) != TFM_SUCCESS) {
tfm_secure_api_error_handler();
diff --git a/secure_fw/core/tfm_secure_api.h b/secure_fw/core/tfm_secure_api.h
index 2f253e1..bc9bfda 100644
--- a/secure_fw/core/tfm_secure_api.h
+++ b/secure_fw/core/tfm_secure_api.h
@@ -29,6 +29,9 @@
#define TFM_ERROR_STATUS(status) (TFM_PARTITION_BUSY)
#endif
+#define TFM_SFN_API_LEGACY 0
+#define TFM_SFN_API_IOVEC 1
+
#ifndef TFM_LVL
#error TFM_LVL is not defined!
#endif
@@ -42,6 +45,7 @@
sfn_t sfn;
int32_t *args;
uint32_t caller_part_idx;
+ int32_t iovec_api;
int32_t ns_caller : 1;
};
@@ -83,12 +87,16 @@
int32_t tfm_core_sfn_request_thread_mode(struct tfm_sfn_req_s *desc_ptr);
+#define TFM_CORE_IOVEC_SFN_REQUEST(id, fn, a, b, c, d) \
+ return tfm_core_partition_request(id, fn, TFM_SFN_API_IOVEC, \
+ (int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d)
+
#define TFM_CORE_SFN_REQUEST(id, fn, a, b, c, d) \
- return tfm_core_partition_request(id, fn, (int32_t)a, (int32_t)b, \
- (int32_t)c, (int32_t)d)
+ return tfm_core_partition_request(id, fn, TFM_SFN_API_LEGACY, \
+ (int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d)
__attribute__ ((always_inline)) __STATIC_INLINE
-int32_t tfm_core_partition_request(uint32_t id, void *fn,
+int32_t tfm_core_partition_request(uint32_t id, void *fn, int32_t iovec_api,
int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4)
{
int32_t args[4] = {arg1, arg2, arg3, arg4};
@@ -98,6 +106,7 @@
desc.sfn = fn;
desc.args = args;
desc.ns_caller = cmse_nonsecure_caller();
+ desc.iovec_api = iovec_api;
if (__get_active_exc_num() != EXC_NUM_THREAD_MODE) {
/* FixMe: Error severity TBD */
return TFM_ERROR_GENERIC;
diff --git a/secure_fw/ns_callable/tfm_veneers.c b/secure_fw/ns_callable/tfm_veneers.c
index 88d4789..aa77922 100644
--- a/secure_fw/ns_callable/tfm_veneers.c
+++ b/secure_fw/ns_callable/tfm_veneers.c
@@ -85,9 +85,9 @@
struct psa_outvec *out_vec, \
size_t out_len) \
{ \
- TFM_CORE_SFN_REQUEST(partition_name##_ID, \
- sfn_name, \
- in_vec, in_len, out_vec, out_len); \
+ TFM_CORE_IOVEC_SFN_REQUEST(partition_name##_ID, \
+ sfn_name, \
+ in_vec, in_len, out_vec, out_len); \
}
/******** TFM_SP_STORAGE ********/
diff --git a/secure_fw/ns_callable/tfm_veneers.c.template b/secure_fw/ns_callable/tfm_veneers.c.template
index 1624747..d67c16e 100644
--- a/secure_fw/ns_callable/tfm_veneers.c.template
+++ b/secure_fw/ns_callable/tfm_veneers.c.template
@@ -30,9 +30,9 @@
struct psa_outvec *out_vec, \
size_t out_len) \
{ \
- TFM_CORE_SFN_REQUEST(partition_name##_ID, \
- sfn_name, \
- in_vec, in_len, out_vec, out_len); \
+ TFM_CORE_IOVEC_SFN_REQUEST(partition_name##_ID, \
+ sfn_name, \
+ in_vec, in_len, out_vec, out_len); \
}
@!GENERATOR_BLOCK_START!@
diff --git a/secure_fw/spm/spm_api.c b/secure_fw/spm/spm_api.c
index 2aeb903..d95bee4 100644
--- a/secure_fw/spm/spm_api.c
+++ b/secure_fw/spm/spm_api.c
@@ -151,6 +151,7 @@
desc.args = args;
desc.ns_caller = 0;
+ desc.iovec_api = TFM_SFN_API_IOVEC;
desc.sfn = (sfn_t)part->static_data.partition_init;
desc.sp_id = part->static_data.partition_id;
res = tfm_core_sfn_request(&desc);