boot: Add ram-load upgrade mode

This patch introduces the ram-load mode in addition to the other
upgrade modes (swap strategies, overwrite-only, direct-XIP). When
ram-load is enabled with the MCUBOOT_RAM_LOAD option, mcuboot
selects the newest valid image based on the image version numbers from
the image header, thereafter the selected image loaded to the RAM and
executed from there. Load address is extracted from the image header.
Therefore the images must be linked to the RAM memory region.
The ram-load mode is very similar to the direct-XIP mode, main
difference is to load the newest image to the RAM beforehand the
authentication and execution. Similar to direct-XIP mode either
of the primary and the secondary slots can hold the active image.

Ram-load can be useful in case of a bit more powerful SoC, which
is not constrained in terms of internal RAM. It could be that image
is stored in external and therefore untrusted flash. Loading image
to internal (trusted) RAM is essential from the security point
of view the system. Furthermore execution from internal RAM is much
faster than from external flash.

This patch is based on the RAM_LOADING upgrade strategy which was
first introduced in the Trusted Firmware-M project.
Source TF-M version: TF-Mv1.0.

Change-Id: I95f02ff07c1dee51244ac372284f449c2efab362
Signed-off-by: Tamas Ban <tamas.ban@arm.com>
diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h
index 171b409..fe96113 100644
--- a/boot/bootutil/src/bootutil_priv.h
+++ b/boot/bootutil/src/bootutil_priv.h
@@ -28,6 +28,8 @@
 #ifndef H_BOOTUTIL_PRIV_
 #define H_BOOTUTIL_PRIV_
 
+#include <string.h>
+
 #include "sysflash/sysflash.h"
 
 #include <flash_map_backend/flash_map_backend.h>
@@ -68,13 +70,15 @@
 
 #if (defined(MCUBOOT_OVERWRITE_ONLY) + \
      defined(MCUBOOT_SWAP_USING_MOVE) + \
-     defined(MCUBOOT_DIRECT_XIP)) > 1
-#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY, MCUBOOT_SWAP_USING_MOVE or MCUBOOT_DIRECT_XIP"
+     defined(MCUBOOT_DIRECT_XIP) + \
+     defined(MCUBOOT_RAM_LOAD)) > 1
+#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY, MCUBOOT_SWAP_USING_MOVE, MCUBOOT_DIRECT_XIP or MCUBOOT_RAM_LOAD"
 #endif
 
 #if !defined(MCUBOOT_OVERWRITE_ONLY) && \
     !defined(MCUBOOT_SWAP_USING_MOVE) && \
-    !defined(MCUBOOT_DIRECT_XIP)
+    !defined(MCUBOOT_DIRECT_XIP) && \
+    !defined(MCUBOOT_RAM_LOAD)
 #define MCUBOOT_SWAP_USING_SCRATCH 1
 #endif
 
@@ -170,18 +174,18 @@
 
 _Static_assert(BOOT_IMAGE_NUMBER > 0, "Invalid value for BOOT_IMAGE_NUMBER");
 
-#if !defined(MCUBOOT_DIRECT_XIP)
-#define IS_IN_XIP_MODE()    0
+#if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD)
+#define ARE_SLOTS_EQUIVALENT()    0
 #else
-#define IS_IN_XIP_MODE()    1
+#define ARE_SLOTS_EQUIVALENT()    1
 
 #if (BOOT_IMAGE_NUMBER != 1)
-#error "The MCUBOOT_DIRECT_XIP mode only supports single-image boot (MCUBOOT_IMAGE_NUMBER=1)."
+#error "The MCUBOOT_DIRECT_XIP and MCUBOOT_RAM_LOAD mode only supports single-image boot (MCUBOOT_IMAGE_NUMBER=1)."
 #endif
 #ifdef MCUBOOT_ENC_IMAGES
-#error "Image encryption (MCUBOOT_ENC_IMAGES) is not supported when MCUBOOT_DIRECT_XIP mode is selected."
+#error "Image encryption (MCUBOOT_ENC_IMAGES) is not supported when MCUBOOT_DIRECT_XIP or MCUBOOT_RAM_LOAD mode is selected."
 #endif
-#endif /* MCUBOOT_DIRECT_XIP */
+#endif /* MCUBOOT_DIRECT_XIP || MCUBOOT_RAM_LOAD */
 
 #define BOOT_MAX_IMG_SECTORS       MCUBOOT_MAX_IMG_SECTORS
 
@@ -439,6 +443,15 @@
 
 #endif  /* !defined(MCUBOOT_USE_FLASH_AREA_GET_SECTORS) */
 
+#ifdef MCUBOOT_RAM_LOAD
+#define LOAD_IMAGE_DATA(hdr, fap, start, output, size)       \
+    (memcpy((output),(void*)((hdr)->ih_load_addr + (start)), \
+    (size)) != (output))
+#else
+#define LOAD_IMAGE_DATA(hdr, fap, start, output, size)       \
+    (flash_area_read((fap), (start), (output), (size)))
+#endif /* MCUBOOT_RAM_LOAD */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/boot/bootutil/src/image_validate.c b/boot/bootutil/src/image_validate.c
index 7071d7d..b5d259a 100644
--- a/boot/bootutil/src/image_validate.c
+++ b/boot/bootutil/src/image_validate.c
@@ -73,12 +73,18 @@
     uint32_t blk_off;
     uint32_t tlv_off;
 
-#if (BOOT_IMAGE_NUMBER == 1) || !defined(MCUBOOT_ENC_IMAGES)
+#if (BOOT_IMAGE_NUMBER == 1) || !defined(MCUBOOT_ENC_IMAGES) || \
+    defined(MCUBOOT_RAM_LOAD)
     (void)enc_state;
     (void)image_index;
     (void)hdr_size;
     (void)blk_off;
     (void)tlv_off;
+#ifdef MCUBOOT_RAM_LOAD
+    (void)blk_sz;
+    (void)off;
+    (void)rc;
+#endif
 #endif
 
 #ifdef MCUBOOT_ENC_IMAGES
@@ -105,6 +111,9 @@
     /* If protected TLVs are present they are also hashed. */
     size += hdr->ih_protect_tlv_size;
 
+#ifdef MCUBOOT_RAM_LOAD
+    bootutil_sha256_update(&sha256_ctx,(void*)(hdr->ih_load_addr), size);
+#else
     for (off = 0; off < size; off += blk_sz) {
         blk_sz = size - off;
         if (blk_sz > tmp_buf_sz) {
@@ -139,6 +148,7 @@
 #endif
         bootutil_sha256_update(&sha256_ctx, tmp_buf, blk_sz);
     }
+#endif /* MCUBOOT_RAM_LOAD */
     bootutil_sha256_finish(&sha256_ctx, hash_result);
 
     return 0;
@@ -291,7 +301,7 @@
         return BOOT_EBADIMAGE;
     }
 
-    rc = flash_area_read(fap, off, img_security_cnt, len);
+    rc = LOAD_IMAGE_DATA(hdr, fap, off, img_security_cnt, len);
     if (rc != 0) {
         return BOOT_EFLASH;
     }
@@ -367,7 +377,7 @@
             if (len != sizeof(hash)) {
                 return -1;
             }
-            rc = flash_area_read(fap, off, buf, sizeof hash);
+            rc = LOAD_IMAGE_DATA(hdr, fap, off, buf, sizeof(hash));
             if (rc) {
                 return rc;
             }
@@ -385,7 +395,7 @@
             if (len > 32) {
                 return -1;
             }
-            rc = flash_area_read(fap, off, buf, len);
+            rc = LOAD_IMAGE_DATA(hdr, fap, off, buf, len);
             if (rc) {
                 return rc;
             }
@@ -402,7 +412,7 @@
             if (len > sizeof(key_buf)) {
                 return -1;
             }
-            rc = flash_area_read(fap, off, key_buf, len);
+            rc = LOAD_IMAGE_DATA(hdr, fap, off, key_buf, len);
             if (rc) {
                 return rc;
             }
@@ -421,7 +431,7 @@
             if (!EXPECTED_SIG_LEN(len) || len > sizeof(buf)) {
                 return -1;
             }
-            rc = flash_area_read(fap, off, buf, len);
+            rc = LOAD_IMAGE_DATA(hdr, fap, off, buf, len);
             if (rc) {
                 return -1;
             }
@@ -442,7 +452,7 @@
                 return -1;
             }
 
-            rc = flash_area_read(fap, off, &img_security_cnt, len);
+            rc = LOAD_IMAGE_DATA(hdr, fap, off, &img_security_cnt, len);
             if (rc) {
                 return rc;
             }
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index c98b841..5a3de4f 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -101,12 +101,12 @@
     return 0;
 }
 
-#ifndef MCUBOOT_DIRECT_XIP
+#if !defined(MCUBOOT_DIRECT_XIP)
 /*
  * Compute the total size of the given image.  Includes the size of
  * the TLVs.
  */
-#if !defined(MCUBOOT_OVERWRITE_ONLY) || defined(MCUBOOT_OVERWRITE_ONLY_FAST)
+#if !defined(MCUBOOT_OVERWRITE_ONLY) ||  defined(MCUBOOT_OVERWRITE_ONLY_FAST)
 static int
 boot_read_image_size(struct boot_loader_state *state, int slot, uint32_t *size)
 {
@@ -165,6 +165,7 @@
 }
 #endif /* !MCUBOOT_OVERWRITE_ONLY */
 
+#if !defined(MCUBOOT_RAM_LOAD)
 static uint32_t
 boot_write_sz(struct boot_loader_state *state)
 {
@@ -379,6 +380,7 @@
     flash_area_close(fap);
     return rc;
 }
+#endif /* !MCUBOOT_RAM_LOAD */
 #endif /* !MCUBOOT_DIRECT_XIP */
 
 /*
@@ -421,7 +423,7 @@
     return 0;
 }
 
-#ifndef MCUBOOT_DIRECT_XIP
+#if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD)
 static int
 split_image_check(struct image_header *app_hdr,
                   const struct flash_area *app_fap,
@@ -450,7 +452,7 @@
 
     return 0;
 }
-#endif /* !MCUBOOT_DIRECT_XIP */
+#endif /* !MCUBOOT_DIRECT_XIP && !MCUBOOT_RAM_LOAD */
 
 /*
  * Check that this is a valid header.  Valid means that the magic is
@@ -522,6 +524,7 @@
 
 #if (BOOT_IMAGE_NUMBER > 1) || \
     defined(MCUBOOT_DIRECT_XIP) || \
+    defined(MCUBOOT_RAM_LOAD) || \
     (defined(MCUBOOT_OVERWRITE_ONLY) && defined(MCUBOOT_DOWNGRADE_PREVENTION))
 /**
  * Compare image version numbers not including the build number
@@ -630,7 +633,7 @@
 #endif
 
     if (!boot_is_header_valid(hdr, fap) || boot_image_check(state, hdr, fap, bs)) {
-        if ((slot != BOOT_PRIMARY_SLOT) || IS_IN_XIP_MODE()) {
+        if ((slot != BOOT_PRIMARY_SLOT) || ARE_SLOTS_EQUIVALENT()) {
             flash_area_erase(fap, 0, fap->fa_size);
             /* Image is invalid, erase it to prevent further unnecessary
              * attempts to validate and boot it.
@@ -697,7 +700,7 @@
 }
 #endif /* MCUBOOT_HW_ROLLBACK_PROT */
 
-#ifndef MCUBOOT_DIRECT_XIP
+#if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD)
 /**
  * Determines which swap operation to perform, if any.  If it is determined
  * that a swap operation is required, the image in the secondary slot is checked
@@ -1932,7 +1935,7 @@
     return rc;
 }
 
-#else /* MCUBOOT_DIRECT_XIP */
+#else /* MCUBOOT_DIRECT_XIP || MCUBOOT_RAM_LOAD */
 
 /**
  * Iterates over all slots and determines which contain a firmware image.
@@ -1975,6 +1978,149 @@
     return image_cnt;
 }
 
+#ifdef MCUBOOT_RAM_LOAD
+
+#if !defined(IMAGE_EXECUTABLE_RAM_START) || !defined(IMAGE_EXECUTABLE_RAM_SIZE)
+#error "Platform MUST define executable RAM bounds in case of RAM_LOAD"
+#endif
+
+/**
+ * Verifies that the image in a slot will be loaded within the predefined bounds
+ * that are allowed to be used by executable images.
+ *
+ * @param img_dst         The address to which the image is going to be copied.
+ * @param img_sz          The size of the image.
+ *
+ * @return                0 on success; nonzero on failure.
+ */
+static int
+boot_verify_ram_load_address(uint32_t img_dst, uint32_t img_sz)
+{
+    uint32_t img_end_addr;
+
+    if (img_dst < IMAGE_EXECUTABLE_RAM_START) {
+        return BOOT_EBADIMAGE;
+    }
+
+    if (!boot_u32_safe_add(&img_end_addr, img_dst, img_sz)) {
+        return BOOT_EBADIMAGE;
+    }
+
+    if (img_end_addr > (IMAGE_EXECUTABLE_RAM_START +
+                        IMAGE_EXECUTABLE_RAM_SIZE)) {
+        return BOOT_EBADIMAGE;
+    }
+
+    return 0;
+}
+
+/**
+ * Copies an image from a slot in the flash to an SRAM address.
+ *
+ * @param  slot     The flash slot of the image to be copied to SRAM.
+ * @param  img_dst  The address at which the image needs to be copied to
+ *                  SRAM.
+ * @param  img_sz   The size of the image that needs to be copied to SRAM.
+ *
+ * @return          0 on success; nonzero on failure.
+ */
+static int
+boot_copy_image_to_sram(int slot, uint32_t img_dst, uint32_t img_sz)
+{
+    int rc;
+    const struct flash_area *fap_src = NULL;
+
+    rc = flash_area_open(flash_area_id_from_image_slot(slot), &fap_src);
+    if (rc != 0) {
+        return BOOT_EFLASH;
+    }
+
+    /* Direct copy from flash to its new location in SRAM. */
+    rc = flash_area_read(fap_src, 0, (void *)img_dst, img_sz);
+    if (rc != 0) {
+        BOOT_LOG_INF("Error whilst copying image from Flash to SRAM: %d", rc);
+    }
+
+    flash_area_close(fap_src);
+
+    return rc;
+}
+
+/**
+ * Copies an image from a slot in the flash to an SRAM address. The load
+ * address and image size is extracted from the image header.
+ *
+ * @param  state    Boot loader status information.
+ * @param  slot     The flash slot of the image to be copied to SRAM.
+ * @param  hdr      Pointer to the image header structure of the image
+ * @param  img_dst  Pointer to the address at which the image needs to be
+ *                  copied to SRAM.
+ * @param  img_sz   Pointer to the size of the image that needs to be
+ *                  copied to SRAM.
+ *
+ * @return          0 on success; nonzero on failure.
+ */
+static int
+boot_load_image_to_sram(struct boot_loader_state *state, uint32_t slot,
+                        struct image_header *hdr, uint32_t *img_dst,
+                        uint32_t *img_sz)
+{
+    int rc;
+
+    if (hdr->ih_flags & IMAGE_F_RAM_LOAD) {
+
+        *img_dst = hdr->ih_load_addr;
+
+        rc = boot_read_image_size(state, slot, img_sz);
+        if (rc != 0) {
+            return rc;
+        }
+
+        rc = boot_verify_ram_load_address(*img_dst, *img_sz);
+        if (rc != 0) {
+            BOOT_LOG_INF("Image RAM load address 0x%x is invalid.", *img_dst);
+            return rc;
+        }
+
+        /* Copy image to the load address from where it currently resides in
+         * flash.
+         */
+        rc = boot_copy_image_to_sram(slot, *img_dst, *img_sz);
+        if (rc != 0) {
+            BOOT_LOG_INF("RAM loading to 0x%x is failed.", *img_dst);
+        } else {
+            BOOT_LOG_INF("RAM loading to 0x%x is succeeded.", *img_dst);
+        }
+    } else {
+        /* Only images that support IMAGE_F_RAM_LOAD are allowed if
+         * MCUBOOT_RAM_LOAD is set.
+         */
+        rc = BOOT_EBADIMAGE;
+    }
+
+    return rc;
+}
+
+/**
+ * Removes an image from SRAM, by overwriting it with zeros.
+ *
+ * @param  img_dst  The address of the image that needs to be removed from
+ *                  SRAM.
+ * @param  img_sz   The size of the image that needs to be removed from
+ *                  SRAM.
+ *
+ * @return          0 on success; nonzero on failure.
+ */
+static inline int
+boot_remove_image_from_sram(uint32_t img_dst, uint32_t img_sz)
+{
+    BOOT_LOG_INF("Removing image from SRAM at address 0x%x", img_dst);
+    memset((void*)img_dst, 0, img_sz);
+
+    return 0;
+}
+#endif /* MCUBOOT_RAM_LOAD */
+
 int
 context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
 {
@@ -1987,6 +2133,11 @@
     uint32_t i;
     int fa_id;
     int rc;
+#ifdef MCUBOOT_RAM_LOAD
+    uint32_t img_dst;
+    uint32_t img_sz;
+    uint32_t img_loaded = 0;
+#endif /* MCUBOOT_RAM_LOAD */
 
     memset(state, 0, sizeof(struct boot_loader_state));
 
@@ -2036,7 +2187,22 @@
                     selected_image_header = hdr;
                 }
             }
-
+#ifdef MCUBOOT_RAM_LOAD
+            /* Image is first loaded to RAM and authenticated there in order to
+             * prevent TOCTOU attack during image copy. This could be applied
+             * when loading images from external (untrusted) flash to internal
+             * (trusted) RAM and image is authenticated before copying.
+             */
+            rc = boot_load_image_to_sram(state, selected_slot,
+                                         selected_image_header, &img_dst,
+                                         &img_sz);
+            if (rc != 0 ) {
+                /* Image loading failed try the next one. */
+                continue;
+            } else {
+                img_loaded = 1;
+            }
+#endif /* MCUBOOT_RAM_LOAD */
             rc = boot_validate_slot(state, selected_slot, NULL);
             if (rc == 0) {
                 /* If a valid image is found then there is no reason to check
@@ -2045,6 +2211,15 @@
                  */
                 break;
             }
+#ifdef MCUBOOT_RAM_LOAD
+            else if (img_loaded) {
+                /* If an image is found to be invalid then it is removed from
+                 * RAM to prevent it being a shellcode vector.
+                 */
+                boot_remove_image_from_sram(img_dst, img_sz);
+                img_loaded = 0;
+            }
+#endif /* MCUBOOT_RAM_LOAD */
             /* The selected image is invalid, mark its slot as "unused"
              * and start over.
              */
@@ -2106,7 +2281,7 @@
    }
    return rc;
 }
-#endif /* MCUBOOT_DIRECT_XIP */
+#endif /* MCUBOOT_DIRECT_XIP || MCUBOOT_RAM_LOAD */
 
 /**
  * Prepares the booting process.  This function moves images around in flash as
diff --git a/boot/bootutil/src/swap_misc.c b/boot/bootutil/src/swap_misc.c
index 4c19d56..44b364d 100644
--- a/boot/bootutil/src/swap_misc.c
+++ b/boot/bootutil/src/swap_misc.c
@@ -32,7 +32,6 @@
 MCUBOOT_LOG_MODULE_DECLARE(mcuboot);
 
 #if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE)
-
 int
 swap_erase_trailer_sectors(const struct boot_loader_state *state,
                            const struct flash_area *fap)
diff --git a/boot/bootutil/src/swap_scratch.c b/boot/bootutil/src/swap_scratch.c
index e60d93d..ed88dc8 100644
--- a/boot/bootutil/src/swap_scratch.c
+++ b/boot/bootutil/src/swap_scratch.c
@@ -82,7 +82,7 @@
     return rc;
 }
 
-#if !defined(MCUBOOT_DIRECT_XIP)
+#if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD)
 /**
  * Reads the status of a partially-completed swap, if any.  This is necessary
  * to recover in case the boot lodaer was reset in the middle of a swap
@@ -724,6 +724,6 @@
 }
 #endif /* !MCUBOOT_OVERWRITE_ONLY */
 
-#endif /* !MCUBOOT_DIRECT_XIP */
+#endif /* !MCUBOOT_DIRECT_XIP && !MCUBOOT_RAM_LOAD */
 
 #endif /* !MCUBOOT_SWAP_USING_MOVE */
diff --git a/boot/bootutil/src/tlv.c b/boot/bootutil/src/tlv.c
index 514121b..37d12a3 100644
--- a/boot/bootutil/src/tlv.c
+++ b/boot/bootutil/src/tlv.c
@@ -2,6 +2,7 @@
  * SPDX-License-Identifier: Apache-2.0
  *
  * Copyright (c) 2019 JUUL Labs
+ * Copyright (c) 2020 Arm Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -46,7 +47,7 @@
     }
 
     off_ = BOOT_TLV_OFF(hdr);
-    if (flash_area_read(fap, off_, &info, sizeof(info))) {
+    if (LOAD_IMAGE_DATA(hdr, fap, off_, &info, sizeof(info))) {
         return -1;
     }
 
@@ -55,7 +56,8 @@
             return -1;
         }
 
-        if (flash_area_read(fap, off_ + info.it_tlv_tot, &info, sizeof(info))) {
+        if (LOAD_IMAGE_DATA(hdr, fap, off_ + info.it_tlv_tot,
+                            &info, sizeof(info))) {
             return -1;
         }
     } else if (hdr->ih_protect_tlv_size != 0) {
@@ -105,7 +107,7 @@
             it->tlv_off += sizeof(struct image_tlv_info);
         }
 
-        rc = flash_area_read(it->fap, it->tlv_off, &tlv, sizeof tlv);
+        rc = LOAD_IMAGE_DATA(it->hdr, it->fap, it->tlv_off, &tlv, sizeof tlv);
         if (rc) {
             return -1;
         }