bootutil: Introduce HW rollback protection

- Add image security counter verification (read security counter value
  from the image manifest and compare it against the stored/active
  security counter) as an optional part of the image validation process
  to prevent the restoration of older, potentially vulnerable images.
- This feature can be enabled with the MCUBOOT_HW_ROLLBACK_PROT option.
- Add security counter interface to MCUBoot. If HW rollback protection
  is enabled then the platform must provide a mechanism to store and
  read the security counter value in a robust and secure way.

Change-Id: Iee4961c1da5275a98ef17982a65b361370d2a178
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 b8491f4..5f68fa8 100644
--- a/boot/bootutil/include/bootutil/image.h
+++ b/boot/bootutil/include/bootutil/image.h
@@ -18,7 +18,7 @@
  */
 
 /*
- * Modifications are Copyright (c) 2019 Arm Limited.
+ * Modifications are Copyright (c) 2019-2020 Arm Limited.
  */
 
 #ifndef H_IMAGE_
@@ -84,6 +84,7 @@
 #define IMAGE_TLV_ENC_KW128         0x31   /* Key encrypted with AES-KW-128 */
 #define IMAGE_TLV_ENC_EC256         0x32   /* Key encrypted with ECIES-EC256 */
 #define IMAGE_TLV_DEPENDENCY        0x40   /* Image depends on other image */
+#define IMAGE_TLV_SEC_CNT           0x50   /* security counter */
 #define IMAGE_TLV_ANY               0xffff /* Used to iterate over all TLV */
 
 struct image_version {
@@ -163,6 +164,10 @@
 int bootutil_tlv_iter_next(struct image_tlv_iter *it, uint32_t *off,
                            uint16_t *len, uint16_t *type);
 
+int32_t bootutil_get_img_security_cnt(struct image_header *hdr,
+                                      const struct flash_area *fap,
+                                      uint32_t *security_cnt);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/boot/bootutil/include/bootutil/security_cnt.h b/boot/bootutil/include/bootutil/security_cnt.h
new file mode 100644
index 0000000..f9791cf
--- /dev/null
+++ b/boot/bootutil/include/bootutil/security_cnt.h
@@ -0,0 +1,69 @@
+/*
+ *  Copyright (c) 2019-2020, Arm Limited. All rights reserved.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef __SECURITY_CNT_H__
+#define __SECURITY_CNT_H__
+
+/**
+ * @file security_cnt.h
+ *
+ * @note The interface must be implemented in a fail-safe way that is
+ *       resistant to asynchronous power failures or it can use hardware
+ *       counters that have this capability, if supported by the platform.
+ *       When a counter incrementation was interrupted it must be able to
+ *       continue the incrementation process or recover the previous consistent
+ *       status of the counters. If the counters have reached a stable status
+ *       (every counter incrementation operation has finished), from that point
+ *       their value cannot decrease due to any kind of power failure.
+ *
+ * @note A security counter might be implemented using non-volatile OTP memory
+ *       (i.e. fuses) in which case it is the responsibility of the platform
+ *       code to map each possible security counter values onto the fuse bits
+ *       as the direct usage of counter values can be costly / impractical.
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Initialises the security counters.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int32_t boot_nv_security_counter_init(void);
+
+/**
+ * Reads the stored value of a given image's security counter.
+ *
+ * @param image_id          Index of the image (from 0).
+ * @param security_cnt      Pointer to store the security counter value.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int32_t boot_nv_security_counter_get(uint32_t image_id, uint32_t *security_cnt);
+
+/**
+ * Updates the stored value of a given image's security counter with a new
+ * security counter value if the new one is greater.
+ *
+ * @param image_id          Index of the image (from 0).
+ * @param img_security_cnt  New security counter value. The new value must be
+ *                          between 0 and UINT32_MAX and it must be greater than
+ *                          or equal to the current security counter value.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int32_t boot_nv_security_counter_update(uint32_t image_id,
+                                        uint32_t img_security_cnt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SECURITY_CNT_H__ */
diff --git a/boot/bootutil/src/image_validate.c b/boot/bootutil/src/image_validate.c
index dd379dc..4390306 100644
--- a/boot/bootutil/src/image_validate.c
+++ b/boot/bootutil/src/image_validate.c
@@ -18,10 +18,11 @@
  */
 
 /*
- * Modifications are Copyright (c) 2019 Arm Limited.
+ * Modifications are Copyright (c) 2019-2020 Arm Limited.
  */
 
 #include <stddef.h>
+#include <stdint.h>
 #include <inttypes.h>
 #include <string.h>
 
@@ -30,6 +31,7 @@
 #include "bootutil/image.h"
 #include "bootutil/sha256.h"
 #include "bootutil/sign_key.h"
+#include "bootutil/security_cnt.h"
 
 #include "mcuboot_config/mcuboot_config.h"
 
@@ -200,6 +202,68 @@
 }
 #endif
 
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+/**
+ * Reads the value of an image's security counter.
+ *
+ * @param hdr           Pointer to the image header structure.
+ * @param fap           Pointer to a description structure of the image's
+ *                      flash area.
+ * @param security_cnt  Pointer to store the security counter value.
+ *
+ * @return              0 on success; nonzero on failure.
+ */
+int32_t
+bootutil_get_img_security_cnt(struct image_header *hdr,
+                              const struct flash_area *fap,
+                              uint32_t *img_security_cnt)
+{
+    struct image_tlv_iter it;
+    uint32_t off;
+    uint16_t len;
+    int32_t rc;
+
+    if ((hdr == NULL) ||
+        (fap == NULL) ||
+        (img_security_cnt == NULL)) {
+        /* Invalid parameter. */
+        return BOOT_EBADARGS;
+    }
+
+    /* The security counter TLV is in the protected part of the TLV area. */
+    if (hdr->ih_protect_tlv_size == 0) {
+        return BOOT_EBADIMAGE;
+    }
+
+    rc = bootutil_tlv_iter_begin(&it, hdr, fap, IMAGE_TLV_SEC_CNT, true);
+    if (rc) {
+        return rc;
+    }
+
+    /* Traverse through the protected TLV area to find
+     * the security counter TLV.
+     */
+
+    rc = bootutil_tlv_iter_next(&it, &off, &len, NULL);
+    if (rc != 0) {
+        /* Security counter TLV has not been found. */
+        return -1;
+    }
+
+    if (len != sizeof(*img_security_cnt)) {
+        /* Security counter is not valid. */
+        return BOOT_EBADIMAGE;
+    }
+
+    rc = flash_area_read(fap, off, img_security_cnt, len);
+    if (rc != 0) {
+        return BOOT_EFLASH;
+    }
+
+    return 0;
+}
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
+
 /*
  * Verify the integrity of the image.
  * Return non-zero if image could not be validated/does not validate.
@@ -222,6 +286,11 @@
     uint8_t buf[SIG_BUF_SIZE];
     uint8_t hash[32];
     int rc;
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+    uint32_t security_cnt = UINT32_MAX;
+    uint32_t img_security_cnt = 0;
+    int32_t security_counter_valid = 0;
+#endif
 
     rc = bootutil_img_hash(enc_state, image_index, hdr, fap, tmp_buf,
             tmp_buf_sz, hash, seed, seed_len);
@@ -302,19 +371,53 @@
                 valid_signature = 1;
             }
             key_id = -1;
-#endif
+#endif /* EXPECTED_SIG_TLV */
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+        } else if (type == IMAGE_TLV_SEC_CNT) {
+            /*
+             * Verify the image's security counter.
+             * This must always be present.
+             */
+            if (len != sizeof(img_security_cnt)) {
+                /* Security counter is not valid. */
+                return -1;
+            }
+
+            rc = flash_area_read(fap, off, &img_security_cnt, len);
+            if (rc) {
+                return rc;
+            }
+
+            rc = boot_nv_security_counter_get(image_index, &security_cnt);
+            if (rc) {
+                return rc;
+            }
+
+            /* Compare the new image's security counter value against the
+             * stored security counter value.
+             */
+            if (img_security_cnt < security_cnt) {
+                /* The image's security counter is not accepted. */
+                return -1;
+            }
+
+            /* The image's security counter has been successfully verified. */
+            security_counter_valid = 1;
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
         }
     }
 
     if (!sha256_valid) {
         return -1;
-    }
-
 #ifdef EXPECTED_SIG_TLV
-    if (!valid_signature) {
+    } else if (!valid_signature) {
         return -1;
-    }
 #endif
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+    } else if (!security_counter_valid) {
+        return -1;
+#endif
+    }
 
     return 0;
 }
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index 4b8a3a2..9d4f054 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -18,7 +18,7 @@
  */
 
 /*
- * Modifications are Copyright (c) 2019 Arm Limited.
+ * Modifications are Copyright (c) 2019-2020 Arm Limited.
  */
 
 /**
@@ -38,6 +38,7 @@
 #include "bootutil_priv.h"
 #include "swap_priv.h"
 #include "bootutil/bootutil_log.h"
+#include "bootutil/security_cnt.h"
 
 #ifdef MCUBOOT_ENC_IMAGES
 #include "bootutil/enc_key.h"
@@ -374,7 +375,7 @@
 }
 
 /*
- * Validate image hash/signature in a slot.
+ * Validate image hash/signature and optionally the security counter in a slot.
  */
 static int
 boot_image_check(struct boot_loader_state *state, struct image_header *hdr,
@@ -649,6 +650,51 @@
     return swap_type;
 }
 
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+/**
+ * Updates the stored security counter value with the image's security counter
+ * value which resides in the given slot, only if it's greater than the stored
+ * value.
+ *
+ * @param image_index   Index of the image to determine which security
+ *                      counter to update.
+ * @param slot          Slot number of the image.
+ * @param hdr           Pointer to the image header structure of the image
+ *                      that is currently stored in the given slot.
+ *
+ * @return              0 on success; nonzero on failure.
+ */
+static int
+boot_update_security_counter(uint8_t image_index, int slot,
+                             struct image_header *hdr)
+{
+    const struct flash_area *fap = NULL;
+    uint32_t img_security_cnt;
+    int rc;
+
+    rc = flash_area_open(flash_area_id_from_multi_image_slot(image_index, slot),
+                         &fap);
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+
+    rc = bootutil_get_img_security_cnt(hdr, fap, &img_security_cnt);
+    if (rc != 0) {
+        goto done;
+    }
+
+    rc = boot_nv_security_counter_update(image_index, img_security_cnt);
+    if (rc != 0) {
+        goto done;
+    }
+
+done:
+    flash_area_close(fap);
+    return rc;
+}
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
+
 /**
  * Erases a region of flash.
  *
@@ -860,6 +906,20 @@
                  size);
     rc = boot_copy_region(state, fap_secondary_slot, fap_primary_slot, 0, 0, size);
 
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+    /* Update the stored security counter with the new image's security counter
+     * value. Both slots hold the new image at this point, but the secondary
+     * slot's image header must be passed since the image headers in the
+     * boot_data structure have not been updated yet.
+     */
+    rc = boot_update_security_counter(BOOT_CURR_IMG(state), BOOT_PRIMARY_SLOT,
+                                boot_img_hdr(state, BOOT_SECONDARY_SLOT));
+    if (rc != 0) {
+        BOOT_LOG_ERR("Security counter update failed after image upgrade.");
+        return rc;
+    }
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
+
     /*
      * Erases header and trailer. The trailer is erased because when a new
      * image is written without a trailer as is the case when using newt, the
@@ -1226,6 +1286,29 @@
         }
     }
 
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+    if (swap_type == BOOT_SWAP_TYPE_PERM) {
+        /* Update the stored security counter with the new image's security
+         * counter value. The primary slot holds the new image at this point,
+         * but the secondary slot's image header must be passed since image
+         * headers in the boot_data structure have not been updated yet.
+         *
+         * In case of a permanent image swap mcuboot will never attempt to
+         * revert the images on the next reboot. Therefore, the security
+         * counter must be increased right after the image upgrade.
+         */
+        rc = boot_update_security_counter(
+                                    BOOT_CURR_IMG(state),
+                                    BOOT_PRIMARY_SLOT,
+                                    boot_img_hdr(state, BOOT_SECONDARY_SLOT));
+        if (rc != 0) {
+            BOOT_LOG_ERR("Security counter update failed after "
+                         "image upgrade.");
+            BOOT_SWAP_TYPE(state) = BOOT_SWAP_TYPE_PANIC;
+        }
+    }
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
+
     if (BOOT_IS_UPGRADE(swap_type)) {
         rc = swap_set_copy_done(BOOT_CURR_IMG(state));
         if (rc != 0) {
@@ -1680,7 +1763,31 @@
             rc = BOOT_EBADIMAGE;
             goto out;
         }
-#endif
+#endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT */
+
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+        /* Update the stored security counter with the active image's security
+         * counter value. It will only be updated if the new security counter is
+         * greater than the stored value.
+         *
+         * In case of a successful image swapping when the swap type is TEST the
+         * security counter can be increased only after a reset, when the swap
+         * type is NONE and the image has marked itself "OK" (the image_ok flag
+         * has been set). This way a "revert" can be performed when it's
+         * necessary.
+         */
+        if (BOOT_SWAP_TYPE(state) == BOOT_SWAP_TYPE_NONE) {
+            rc = boot_update_security_counter(
+                                    BOOT_CURR_IMG(state),
+                                    BOOT_PRIMARY_SLOT,
+                                    boot_img_hdr(state, BOOT_PRIMARY_SLOT));
+            if (rc != 0) {
+                BOOT_LOG_ERR("Security counter update failed after image "
+                             "validation.");
+                goto out;
+            }
+        }
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
     }
 
 #if (BOOT_IMAGE_NUMBER > 1)
diff --git a/boot/zephyr/Kconfig b/boot/zephyr/Kconfig
index a5009e4..dacaf3a 100644
--- a/boot/zephyr/Kconfig
+++ b/boot/zephyr/Kconfig
@@ -1,4 +1,5 @@
 # Copyright (c) 2017 Linaro Limited
+# Copyright (c) 2020 Arm Limited
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -361,8 +362,12 @@
 	help
 	  Enables support of multi image update.
 
+choice
+	prompt "Downgrade prevention"
+	optional
+
 config MCUBOOT_DOWNGRADE_PREVENTION
-	bool "Downgrade prevention"
+	bool "SW based downgrade prevention"
 	depends on BOOT_UPGRADE_ONLY
 	help
 	  Prevent downgrades by enforcing incrementing version numbers.
@@ -371,4 +376,14 @@
 	  only protects against some attacks against version downgrades (for
 	  example, a JTAG could be used to write an older version).
 
+config MCUBOOT_HW_DOWNGRADE_PREVENTION
+	bool "HW based downgrade prevention"
+	help
+	  Prevent undesirable/malicious software downgrades. When this option is
+	  set, any upgrade must have greater or equal security counter value.
+	  Because of the acceptance of equal values it allows for software
+	  downgrade to some extent.
+
+endchoice
+
 source "Kconfig.zephyr"
diff --git a/boot/zephyr/include/mcuboot_config/mcuboot_config.h b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
index 14705d3..2b998b5 100644
--- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h
+++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2018 Open Source Foundries Limited
- * Copyright (c) 2019 Arm Limited
+ * Copyright (c) 2019-2020 Arm Limited
  *
  * SPDX-License-Identifier: Apache-2.0
  */
@@ -93,6 +93,10 @@
 #define MCUBOOT_DOWNGRADE_PREVENTION 1
 #endif
 
+#ifdef CONFIG_MCUBOOT_HW_DOWNGRADE_PREVENTION
+#define MCUBOOT_HW_ROLLBACK_PROT
+#endif
+
 /*
  * Enabling this option uses newer flash map APIs. This saves RAM and
  * avoids deprecated API usage.
diff --git a/docs/design.md b/docs/design.md
index b779778..de40dd3 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -18,7 +18,7 @@
 -->
 
 <!--
-  Modifications are Copyright (c) 2019 Arm Limited.
+  Modifications are Copyright (c) 2019-2020 Arm Limited.
 -->
 
 # Boot Loader
@@ -109,6 +109,7 @@
 #define IMAGE_TLV_ENC_KW128         0x31   /* Key encrypted with AES-KW-128 */
 #define IMAGE_TLV_ENC_EC256         0x32   /* Key encrypted with ECIES P256 */
 #define IMAGE_TLV_DEPENDENCY        0x40   /* Image depends on other image */
+#define IMAGE_TLV_SEC_CNT           0x50   /* security counter */
 ```
 
 Optional type-length-value records (TLVs) containing image metadata are placed
@@ -946,7 +947,28 @@
 ## [Downgrade Prevention](#downgrade-prevention)
 
 Downgrade prevention is a feature which enforces that the new image must have a
-higher version number than the image it is replacing. This feature is enabled
-with the `MCUBOOT_DOWNGRADE_PREVENTION` option. Downgrade prevention is only
-available when the overwrite-based image update strategy is used
-(i.e. `MCUBOOT_OVERWRITE_ONLY` is set).
+higher version/security counter number than the image it is replacing, thus
+preventing the malicious downgrading of the device to an older and possibly
+vulnerable version of its firmware.
+
+### [SW Based Downgrade Prevention](#sw-downgrade-prevention)
+
+During the software based downgrade prevention the image version numbers are
+compared. This feature is enabled with the `MCUBOOT_DOWNGRADE_PREVENTION`
+option. In this case downgrade prevention is only available when the
+overwrite-based image update strategy is used (i.e. `MCUBOOT_OVERWRITE_ONLY`
+is set).
+
+### [HW Based Downgrade Prevention](#hw-downgrade-prevention)
+
+Each signed image can contain a security counter in its protected TLV area.
+During the hardware based downgrade prevention (alias rollback protection) the
+new image's security counter will be compared with the currently active security
+counter value which must be stored in a non-volatile and trusted component of
+the device. This feature is enabled with the `MCUBOOT_HW_ROLLBACK_PROT` option.
+It is beneficial to handle this counter independently from image version
+number:
+
+  * It does not need to increase with each software release,
+  * It makes it possible to do software downgrade to some extent: if the
+    security counter has the same value in the older image then it is accepted.