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)&REGION_NAME(Image$$, TFM_UNPRIV_SCRATCH, $$ZI$$Base);
+    uint32_t scratch_limit =
+        (uint32_t)&REGION_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);