Boot: Add dependency check to multi-image boot

This patch adds the capability to check image dependencies in case
of multi-image boot. The dependencies are described with a new type
of TLV in the manifest.

Change-Id: If45f81a00d4324c881634f50156f9939e1bf8707
Signed-off-by: David Vincze <david.vincze@arm.com>
diff --git a/boot/bootutil/include/bootutil/image.h b/boot/bootutil/include/bootutil/image.h
index 22f6e62..88b1a43 100644
--- a/boot/bootutil/include/bootutil/image.h
+++ b/boot/bootutil/include/bootutil/image.h
@@ -17,6 +17,10 @@
  * under the License.
  */
 
+/*
+ * Modifications are Copyright (c) 2019 Arm Limited.
+ */
+
 #ifndef H_IMAGE_
 #define H_IMAGE_
 
@@ -76,6 +80,7 @@
 #define IMAGE_TLV_ED25519           0x24   /* ed25519 of hash output */
 #define IMAGE_TLV_ENC_RSA2048       0x30   /* Key encrypted with RSA-OAEP-2048 */
 #define IMAGE_TLV_ENC_KW128         0x31   /* Key encrypted with AES-KW-128 */
+#define IMAGE_TLV_DEPENDENCY        0x40   /* Image depends on other image */
 
 struct image_version {
     uint8_t iv_major;
@@ -84,16 +89,24 @@
     uint32_t iv_build_num;
 };
 
+struct image_dependency {
+    uint8_t image_id;                       /* Image index (from 0) */
+    struct image_version image_min_version; /* Indicates at minimum which
+                                             * version of firmware must be
+                                             * available to satisfy compliance
+                                             */
+};
+
 /** Image header.  All fields are in little endian byte order. */
 struct image_header {
     uint32_t ih_magic;
     uint32_t ih_load_addr;
-    uint16_t ih_hdr_size; /* Size of image header (bytes). */
-    uint16_t _pad1;
-    uint32_t ih_img_size; /* Does not include header. */
-    uint32_t ih_flags;    /* IMAGE_F_[...]. */
+    uint16_t ih_hdr_size;           /* Size of image header (bytes). */
+    uint16_t ih_protect_tlv_size;   /* Size of protected TLV area (bytes). */
+    uint32_t ih_img_size;           /* Does not include header. */
+    uint32_t ih_flags;              /* IMAGE_F_[...]. */
     struct image_version ih_ver;
-    uint32_t _pad2;
+    uint32_t _pad1;
 };
 
 /** Image TLV header.  All fields in little endian. */
diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c
index b2a07d0..a2fd7e3 100644
--- a/boot/bootutil/src/bootutil_misc.c
+++ b/boot/bootutil/src/bootutil_misc.c
@@ -741,3 +741,38 @@
     flash_area_close(fap);
     return rc;
 }
+
+#if (BOOT_IMAGE_NUMBER > 1)
+/**
+ * Check if the version of the image is not older than required.
+ *
+ * @param req         Required minimal image version.
+ * @param ver         Version of the image to be checked.
+ *
+ * @return            0 if the version is sufficient, nonzero otherwise.
+ */
+int
+boot_is_version_sufficient(struct image_version *req,
+                           struct image_version *ver)
+{
+    if (ver->iv_major > req->iv_major) {
+        return 0;
+    }
+    if (ver->iv_major < req->iv_major) {
+        return BOOT_EBADVERSION;
+    }
+    /* The major version numbers are equal. */
+    if (ver->iv_minor > req->iv_minor) {
+        return 0;
+    }
+    if (ver->iv_minor < req->iv_minor) {
+        return BOOT_EBADVERSION;
+    }
+    /* The minor version numbers are equal. */
+    if (ver->iv_revision < req->iv_revision) {
+        return BOOT_EBADVERSION;
+    }
+
+    return 0;
+}
+#endif /* BOOT_IMAGE_NUMBER > 1 */
diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h
index 09d5fac..5f86c7b 100644
--- a/boot/bootutil/src/bootutil_priv.h
+++ b/boot/bootutil/src/bootutil_priv.h
@@ -48,13 +48,14 @@
 
 struct flash_area;
 
-#define BOOT_EFLASH     1
-#define BOOT_EFILE      2
-#define BOOT_EBADIMAGE  3
-#define BOOT_EBADVECT   4
-#define BOOT_EBADSTATUS 5
-#define BOOT_ENOMEM     6
-#define BOOT_EBADARGS   7
+#define BOOT_EFLASH      1
+#define BOOT_EFILE       2
+#define BOOT_EBADIMAGE   3
+#define BOOT_EBADVECT    4
+#define BOOT_EBADSTATUS  5
+#define BOOT_ENOMEM      6
+#define BOOT_EBADARGS    7
+#define BOOT_EBADVERSION 8
 
 #define BOOT_TMPBUF_SZ  256
 
@@ -241,6 +242,10 @@
                        const uint8_t *enckey);
 int boot_read_enc_key(uint8_t slot, uint8_t *enckey);
 #endif
+#if (BOOT_IMAGE_NUMBER > 1)
+int boot_is_version_sufficient(struct image_version *req,
+                               struct image_version *ver);
+#endif
 
 /*
  * Accessors for the contents of struct boot_loader_state.
diff --git a/boot/bootutil/src/image_validate.c b/boot/bootutil/src/image_validate.c
index 9cff0ac..b4defb2 100644
--- a/boot/bootutil/src/image_validate.c
+++ b/boot/bootutil/src/image_validate.c
@@ -17,6 +17,10 @@
  * under the License.
  */
 
+/*
+ * Modifications are Copyright (c) 2019 Arm Limited.
+ */
+
 #include <assert.h>
 #include <stddef.h>
 #include <inttypes.h>
@@ -78,12 +82,20 @@
     }
 #endif
 
-    /*
-     * Hash is computed over image header and image itself. No TLV is
-     * included ATM.
-     */
+    /* Hash is computed over image header and image itself. */
     hdr_size = hdr->ih_hdr_size;
     size = hdr->ih_img_size + hdr_size;
+
+#if (MCUBOOT_IMAGE_NUMBER > 1)
+    /* If dependency TLVs are present then the TLV info header and the
+     * dependency TLVs are also protected and have to be included in the hash
+     * calculation.
+     */
+    if (hdr->ih_protect_tlv_size != 0) {
+        size += hdr->ih_protect_tlv_size;
+    }
+#endif
+
     for (off = 0; off < size; off += blk_sz) {
         blk_sz = size - off;
         if (blk_sz > tmp_buf_sz) {
@@ -212,7 +224,6 @@
     }
 
     /* The TLVs come after the image. */
-    /* After image there are TLVs. */
     off = hdr->ih_img_size + hdr->ih_hdr_size;
 
     rc = flash_area_read(fap, off, &info, sizeof(info));
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index 4c94fa7..191171f 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -1608,6 +1608,198 @@
 }
 #endif /* !MCUBOOT_OVERWRITE_ONLY */
 
+#if (BOOT_IMAGE_NUMBER > 1)
+/**
+ * Check the image dependency whether it is satisfied and modify
+ * the swap type if necessary.
+ *
+ * @param dep               Image dependency which has to be verified.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+boot_verify_single_dependency(struct image_dependency *dep)
+{
+    struct image_version *dep_version;
+    size_t dep_slot;
+    int rc;
+
+    /* Determine the source of the image which is the subject of
+     * the dependency and get it's version. */
+    dep_slot = (boot_data.swap_type[dep->image_id] != BOOT_SWAP_TYPE_NONE) ?
+                BOOT_SECONDARY_SLOT : BOOT_PRIMARY_SLOT;
+    dep_version = &boot_data.imgs[dep->image_id][dep_slot].hdr.ih_ver;
+
+    rc = boot_is_version_sufficient(&dep->image_min_version, dep_version);
+    if (rc != 0) {
+        /* Dependency not satisfied.
+         * Modify the swap type to decrease the version number of the image
+         * (which will be located in the primary slot after the boot process),
+         * consequently the number of unsatisfied dependencies will be
+         * decreased or remain the same.
+         */
+        switch (BOOT_SWAP_TYPE(&boot_data)) {
+        case BOOT_SWAP_TYPE_TEST:
+        case BOOT_SWAP_TYPE_PERM:
+            BOOT_SWAP_TYPE(&boot_data) = BOOT_SWAP_TYPE_NONE;
+            break;
+        case BOOT_SWAP_TYPE_NONE:
+            BOOT_SWAP_TYPE(&boot_data) = BOOT_SWAP_TYPE_REVERT;
+            break;
+        default:
+            break;
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Read all dependency TLVs of an image from the flash and verify
+ * one after another to see if they are all satisfied.
+ *
+ * @param slot              Image slot number.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+boot_verify_all_dependency(uint32_t slot)
+{
+    const struct flash_area *fap;
+    struct image_header *hdr;
+    struct image_tlv_info info;
+    struct image_tlv tlv;
+    struct image_dependency dep;
+    uint32_t off;
+    uint32_t end;
+    bool dep_tlvs_found = false;
+    int rc;
+
+    rc = flash_area_open(flash_area_id_from_image_slot(slot), &fap);
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+
+    hdr = boot_img_hdr(&boot_data, slot);
+    /* The TLVs come after the image. */
+    off = hdr->ih_hdr_size + hdr->ih_img_size;
+
+    /* The TLV area always starts with an image_tlv_info structure. */
+    rc = flash_area_read(fap, off, &info, sizeof(info));
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+
+    if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
+        rc = BOOT_EBADIMAGE;
+        goto done;
+    }
+    end = off + info.it_tlv_tot;
+    off += sizeof(info);
+
+    /* Traverse through all of the TLVs to find the dependency TLVs. */
+    for (; off < end; off += sizeof(tlv) + tlv.it_len) {
+        rc = flash_area_read(fap, off, &tlv, sizeof(tlv));
+        if (rc != 0) {
+             rc = BOOT_EFLASH;
+             goto done;
+         }
+
+        if (tlv.it_type == IMAGE_TLV_DEPENDENCY) {
+            if (!dep_tlvs_found) {
+                dep_tlvs_found = true;
+            }
+
+            if (tlv.it_len != sizeof(dep)) {
+                rc = BOOT_EBADIMAGE;
+                goto done;
+            }
+
+            rc = flash_area_read(fap, off + sizeof(tlv), &dep, tlv.it_len);
+            if (rc != 0) {
+                rc = BOOT_EFLASH;
+                goto done;
+            }
+
+            /* Verify dependency and modify the swap type if not satisfied. */
+            rc = boot_verify_single_dependency(&dep);
+            if (rc != 0) {
+                /* Dependency not satisfied. */
+                goto done;
+            }
+
+            /* Dependency satisfied, no action needed.
+             * Continue with the next TLV entry.
+             */
+        } else if (dep_tlvs_found) {
+            /* The dependency TLVs are contiguous in the TLV area. If a
+             * dependency had already been found and the last read TLV
+             * has a different type then there are no more dependency TLVs.
+             * The search can be finished.
+             */
+            break;
+        }
+    }
+
+done:
+    flash_area_close(fap);
+    return rc;
+}
+
+/**
+ * Verify whether the image dependencies in the TLV area are
+ * all satisfied and modify the swap type if necessary.
+ *
+ * @return                  0 if all dependencies are satisfied,
+ *                          nonzero otherwise.
+ */
+static int
+boot_verify_single_image_dependency(void)
+{
+    size_t slot;
+
+    /* Determine the source of the dependency TLVs. Those dependencies have to
+     * be checked which belong to the image that will be located in the primary
+     * slot after the firmware update process.
+     */
+    if (BOOT_SWAP_TYPE(&boot_data) != BOOT_SWAP_TYPE_NONE &&
+        BOOT_SWAP_TYPE(&boot_data) != BOOT_SWAP_TYPE_FAIL) {
+        slot = BOOT_SECONDARY_SLOT;
+    } else {
+        slot = BOOT_PRIMARY_SLOT;
+    }
+
+    return boot_verify_all_dependency(slot);
+}
+
+/**
+ * Iterate over all the images and verify whether the image dependencies in the
+ * TLV area are all satisfied and update the related swap type if necessary.
+ */
+static void
+boot_verify_all_image_dependency(void)
+{
+    current_image = 0;
+    int rc;
+
+    while (current_image < BOOT_IMAGE_NUMBER) {
+        rc = boot_verify_single_image_dependency();
+        if ( rc == 0) {
+            /* All dependencies've been satisfied, continue with next image. */
+            current_image++;
+        } else if (rc == BOOT_EBADVERSION) {
+            /* Dependency check needs to be restarted. */
+            current_image = 0;
+        } else {
+            /* Other error happened, images are inconsistent */
+            return;
+        }
+    }
+}
+#endif /* (BOOT_IMAGE_NUMBER > 1) */
+
 /**
  * Performs a clean (not aborted) image update.
  *
@@ -1966,6 +2158,13 @@
         boot_prepare_image_for_update(&bs);
     }
 
+#if (BOOT_IMAGE_NUMBER > 1)
+    /* Iterate over all the images and verify whether the image dependencies
+     * are all satisfied and update swap type if necessary.
+     */
+    boot_verify_all_image_dependency();
+#endif
+
     /* Iterate over all the images. At this point there are no aborted swaps
      * and the swap types are determined for each image. By the end of the loop
      * all required update operations will have been finished.
diff --git a/docs/design.md b/docs/design.md
index 6573c25..93c0383 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -64,12 +64,12 @@
 struct image_header {
     uint32_t ih_magic;
     uint32_t ih_load_addr;
-    uint16_t ih_hdr_size; /* Size of image header (bytes). */
-    uint16_t _pad2;
-    uint32_t ih_img_size; /* Does not include header. */
-    uint32_t ih_flags;    /* IMAGE_F_[...]. */
+    uint16_t ih_hdr_size;           /* Size of image header (bytes). */
+    uint16_t ih_protect_tlv_size;   /* Size of protected TLV area (bytes). */
+    uint32_t ih_img_size;           /* Does not include header. */
+    uint32_t ih_flags;              /* IMAGE_F_[...]. */
     struct image_version ih_ver;
-    uint32_t _pad3;
+    uint32_t _pad1;
 };
 
 /** Image TLV header.  All fields in little endian. */
@@ -102,11 +102,20 @@
 #define IMAGE_TLV_ECDSA256          0x22   /* ECDSA of hash output */
 #define IMAGE_TLV_RSA3072_PSS       0x23   /* RSA3072 of hash output */
 #define IMAGE_TLV_ED25519           0x24   /* ED25519 of hash output */
+#define IMAGE_TLV_ENC_RSA2048       0x30   /* Key encrypted with RSA-OAEP-2048 */
+#define IMAGE_TLV_ENC_KW128         0x31   /* Key encrypted with AES-KW-128 */
+#define IMAGE_TLV_DEPENDENCY        0x40   /* Image depends on other image */
 ```
 
 Optional type-length-value records (TLVs) containing image metadata are placed
 after the end of the image.
 
+The `ih_protect_tlv_size` field indicates the length of the protected TLV area.
+If dependency TLVs are present then the TLV info header and the dependency TLVs
+are also protected and have to be included in the hash calculation. Otherwise
+the hash is only calculated over the image header and the image itself. In this
+case the value of the `ih_protect_tlv_size` field is 0.
+
 The `ih_hdr_size` field indicates the length of the header, and therefore the
 offset of the image itself.  This field provides for backwards compatibility in
 case of changes to the format of the image header.
@@ -522,11 +531,15 @@
 | Scratch            |
 +--------------------+
 ```
-The multiple image boot procedure is organized in loops which iterate over all
-the firmware images. The high-level overview of the boot process is presented
-below.
-
-Procedure:
+MCUBoot is also capable of handling dependencies between images. For example
+if an image needs to be reverted it might be necessary to revert another one too
+(e.g. due to API incompatibilities) or simply to prevent from being updated
+because of an unsatisfied dependency. Therefore all aborted swaps have to be
+completed and all the swap types have to be determined for each image before
+the dependency checks. Dependency handling is described in more detail in a
+following section. The multiple image boot procedure is organized in loops which
+iterate over all the firmware images. The high-level overview of the boot
+process is presented below.
 
 + ###### Loop 1. Iterate over all images
     1. Inspect swap status region of current image; is an interrupted swap being
@@ -557,9 +570,15 @@
             + Skip to next image.
 
 + ###### Loop 2. Iterate over all images
-    At this point there are no aborted swaps and the swap types are determined
-    for each image.
+    1. Does the current image depend on other image(s)?
+        + Yes: Are all the image dependencies satisfied?
+            + Yes: Skip to next image.
+            + No:
+                + Modify swap type depending on what the previous type was.
+                + Restart dependency check from the first image.
+        + No: Skip to next image.
 
++ ###### Loop 3. Iterate over all images
     1. Is an image swap requested?
         + Yes:
             + Perform image update operation.
@@ -567,8 +586,7 @@
             + Skip to next image.
         + No: Skip to next image.
 
-+ ###### Loop 3. Iterate over all images
-
++ ###### Loop 4. Iterate over all images
     1. Validate image in the primary slot (integrity and security check) or
        at least do a basic sanity check to avoid booting into an empty flash
        area.
@@ -842,3 +860,51 @@
 
 If you want to enable and use encrypted images, see:
 [encrypted_images](encrypted_images.md).
+
+## Dependency Check
+
+MCUBoot can handle multiple firmware images. It is possible to update them
+independently but in many cases it can be desired to be able to describe
+dependencies between the images (e.g. to ensure API compliance and avoid
+interoperability issues).
+
+The dependencies between images can be described with additional TLV entries in
+the TLV area after the end of an image. There can be more than one dependency
+entry, but in practice if the platform only supports two individual images then
+there can be maximum one entry which reflects to the other image.
+
+If the TLV area contains dependency TLV entries, then these are required to be
+integrity and authenticity protected. In this case the SHA256 has to be
+calculated over not just the image header and the image but also the TLV info
+header and the dependency TLVs.
+```
+A +---------------------+
+  | Header              | <- struct image_header
+  +---------------------+
+  | Payload             |
+  +---------------------+
+  | TLV area            |
+  | +-----------------+ |
+  | | TLV area header | | <- struct image_tlv_info
+  | +-----------------+ |
+  | | Dependency      | | <- Dependency entry (struct image_tlv)
+B | +-----------------+ |
+  | | SHA256 hash     | | <- hash from A - B (struct image_tlv)
+C | +-----------------+ |
+  | | Keyhash         | | <- indicates which pub. key for sig (struct image_tlv)
+  | +-----------------+ |
+  | | Signature       | | <- signature from B - C (struct image_tlv), only hash
+  | +-----------------+ |
+  +---------------------+
+```
+At the phase of dependency check all aborted swaps are finalized if there were
+any. During the dependency check the boot loader verifies whether the image
+dependencies are all satisfied. If at least one of the dependencies of an image
+is not fulfilled then the swap type of that image has to be modified
+accordingly and the dependency check needs to be restarted. This way the number
+of unsatisfied dependencies will decrease or remain the same. There is always at
+least 1 valid configuration. In worst case, the system returns to the initial
+state after dependency check.
+
+For more information on adding dependency entries to an image,
+see: [imgtool](imgtool.md).
diff --git a/docs/imgtool.md b/docs/imgtool.md
index 9d71746..7e17105 100644
--- a/docs/imgtool.md
+++ b/docs/imgtool.md
@@ -58,6 +58,7 @@
       -k, --key filename
       --align [1|2|4|8]          [required]
       -v, --version TEXT         [required]
+      -d, --dependencies TEXT
       -H, --header-size INTEGER  [required]
       --pad-header               Add --header-size zeroed bytes at the beginning
                                  of the image
@@ -95,3 +96,9 @@
 The optional `--pad` argument will place a trailer on the image that
 indicates that the image should be considered an upgrade.  Writing this image
 in the secondary slot will then cause the bootloader to upgrade to it.
+
+A dependency can be specified in the following way:
+`-d "(image_id, image_version)"`. The `image_id` is the number of the image
+which the current image depends on. The `image_version` is the minimum version
+of that image to satisfy compliance. For example `-d "(1, 1.2.3+0)"` means this
+image depends on Image 1 which version has to be at least 1.2.3+0.