Boot: Introduce rollback protection
- Add image security counter verification (read image security counter
from the manifest and compare it against the stored security counter)
as a mandatory part of the image validation process.
- Store the newest value of security counter in a non-volatile (NV)
counter.
- Add security counter interface to MCUBoot.
Change-Id: I608508e707d01c3777788bc754810407fae610e2
Signed-off-by: David Vincze <david.vincze@arm.com>
diff --git a/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h b/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h
index 918d2d1..1eb5a4a 100644
--- a/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h
+++ b/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h
@@ -44,6 +44,7 @@
#define BOOT_EBADSTATUS 5
#define BOOT_ENOMEM 6
#define BOOT_EBADARGS 7
+#define BOOT_EBADMAGIC 8
#define BOOT_TMPBUF_SZ 256
diff --git a/bl2/ext/mcuboot/bootutil/src/image_validate.c b/bl2/ext/mcuboot/bootutil/src/image_validate.c
index 68f1c40..b9db6de 100644
--- a/bl2/ext/mcuboot/bootutil/src/image_validate.c
+++ b/bl2/ext/mcuboot/bootutil/src/image_validate.c
@@ -33,6 +33,7 @@
#include "bootutil/image.h"
#include "bootutil/sha256.h"
#include "bootutil/sign_key.h"
+#include "security_cnt.h"
#ifdef MCUBOOT_SIGN_RSA
#include "mbedtls/rsa.h"
@@ -164,7 +165,7 @@
info = *((struct image_tlv_info *)(load_address + off));
if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
- return -1;
+ return BOOT_EBADMAGIC;
}
end = off + info.it_tlv_tot;
off += sizeof(info);
@@ -173,7 +174,7 @@
* Traverse through all of the TLVs, performing any checks we know
* and are able to do.
*/
- for (; off < end; off += sizeof(tlv) + tlv.it_len) {
+ while (off < end) {
tlv = *((struct image_tlv *)(load_address + off));
tlv_sz = sizeof(tlv);
@@ -192,6 +193,14 @@
sha256_valid = 1;
}
+
+ /* Avoid integer overflow. */
+ if ((UINT32_MAX - off) < (sizeof(tlv) + tlv.it_len)) {
+ /* Potential overflow. */
+ break;
+ } else {
+ off += sizeof(tlv) + tlv.it_len;
+ }
}
if (!sha256_valid) {
@@ -202,6 +211,97 @@
}
#endif /* MCUBOOT_RAM_LOADING */
+/**
+ * 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_info info;
+ struct image_tlv tlv;
+ uint32_t off;
+ uint32_t end;
+ uint32_t found = 0;
+ int32_t rc;
+
+ if ((hdr == NULL) ||
+ (fap == NULL) ||
+ (img_security_cnt == NULL)) {
+ /* Invalid parameter. */
+ return BOOT_EBADARGS;
+ }
+
+ /* 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) {
+ return BOOT_EFLASH;
+ }
+
+ if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
+ return BOOT_EBADMAGIC;
+ }
+
+ /* The security counter TLV is in the protected part of the TLV area. */
+ if (hdr->ih_protect_tlv_size != 0) {
+ end = off + (uint32_t)hdr->ih_protect_tlv_size;
+ off += sizeof(info);
+
+ /* Traverse through the protected TLV area to find the
+ * security counter TLV.
+ */
+ while (off < end) {
+ rc = flash_area_read(fap, off, &tlv, sizeof(tlv));
+ if (rc != 0) {
+ return BOOT_EFLASH;
+ }
+
+ if (tlv.it_type == IMAGE_TLV_SEC_CNT) {
+
+ if (tlv.it_len != sizeof(*img_security_cnt)) {
+ /* Security counter is not valid. */
+ break;
+ }
+
+ rc = flash_area_read(fap, off + sizeof(tlv),
+ img_security_cnt, tlv.it_len);
+ if (rc != 0) {
+ return BOOT_EFLASH;
+ }
+
+ /* Security counter has been found. */
+ found = 1;
+ break;
+ }
+
+ /* Avoid integer overflow. */
+ if ((UINT32_MAX - off) < (sizeof(tlv) + tlv.it_len)) {
+ /* Potential overflow. */
+ break;
+ } else {
+ off += sizeof(tlv) + tlv.it_len;
+ }
+ }
+ }
+
+ if (found) {
+ return 0;
+ }
+
+ return -1;
+}
+
/*
* Verify the integrity of the image.
* Return non-zero if image could not be validated/does not validate.
@@ -222,6 +322,9 @@
struct image_tlv tlv;
uint8_t buf[256];
uint8_t hash[32] = {0};
+ uint32_t security_cnt;
+ uint32_t img_security_cnt;
+ int32_t security_counter_valid = 0;
int rc;
rc = bootutil_img_hash(hdr, fap, tmp_buf, tmp_buf_sz, hash,
@@ -242,7 +345,7 @@
return rc;
}
if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
- return -1;
+ return BOOT_EBADMAGIC;
}
end = off + info.it_tlv_tot;
off += sizeof(info);
@@ -251,7 +354,7 @@
* Traverse through all of the TLVs, performing any checks we know
* and are able to do.
*/
- for (; off < end; off += sizeof(tlv) + tlv.it_len) {
+ while (off < end) {
rc = flash_area_read(fap, off, &tlv, sizeof(tlv));
if (rc) {
return rc;
@@ -311,10 +414,49 @@
}
key_id = -1;
#endif
+ } else if (tlv.it_type == IMAGE_TLV_SEC_CNT) {
+ /*
+ * Verify the image's security counter.
+ * This must always be present.
+ */
+ if (tlv.it_len != sizeof(img_security_cnt)) {
+ /* Security counter is not valid. */
+ return -1;
+ }
+
+ rc = flash_area_read(fap, off + sizeof(tlv),
+ &img_security_cnt, tlv.it_len);
+ if (rc) {
+ return rc;
+ }
+
+ rc = boot_nv_security_counter_get(0, &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;
+ }
+
+ /* Avoid integer overflow. */
+ if ((UINT32_MAX - off) < (sizeof(tlv) + tlv.it_len)) {
+ /* Potential overflow. */
+ break;
+ } else {
+ off += sizeof(tlv) + tlv.it_len;
}
}
- if (!sha256_valid) {
+ if (!sha256_valid || !security_counter_valid) {
return -1;
}
diff --git a/bl2/ext/mcuboot/bootutil/src/loader.c b/bl2/ext/mcuboot/bootutil/src/loader.c
index 42459c3..0a40342 100644
--- a/bl2/ext/mcuboot/bootutil/src/loader.c
+++ b/bl2/ext/mcuboot/bootutil/src/loader.c
@@ -41,6 +41,7 @@
#include "bootutil_priv.h"
#include "bl2/include/tfm_boot_status.h"
#include "bl2/include/boot_record.h"
+#include "security_cnt.h"
#define BOOT_LOG_LEVEL BOOT_LOG_LEVEL_INFO
#include "bootutil/bootutil_log.h"
@@ -260,8 +261,8 @@
return 0;
}
-/*
- * Validate image hash/signature in a slot.
+/**
+ * Validate image hash/signature and security counter in a slot.
*/
static int
boot_image_check(struct image_header *hdr, const struct flash_area *fap)
@@ -320,6 +321,8 @@
}
BOOT_LOG_ERR("Authentication failed! Image in slot %d is not valid.",
slot);
+
+ flash_area_close(fap);
return -1;
}
@@ -329,6 +332,44 @@
return 0;
}
+/**
+ * Updates the stored security counter value with the image's security counter
+ * value which resides in the given slot if it's greater than the stored value.
+ *
+ * @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(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_image_slot(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(0, img_security_cnt);
+ if (rc != 0) {
+ goto done;
+ }
+
+done:
+ flash_area_close(fap);
+ return rc;
+}
+
#if !defined(MCUBOOT_NO_SWAP) && !defined(MCUBOOT_OVERWRITE_ONLY)
/*
* Compute the total size of the given image. Includes the size of
@@ -1094,6 +1135,17 @@
rc = boot_copy_sector(FLASH_AREA_IMAGE_1, FLASH_AREA_IMAGE_0,
0, 0, size);
+ /* Update the stored security counter with the new image's security counter
+ * value. Both slots hold the new image at this point, but slot 1's image
+ * header must be passed because the read image headers in the boot_data
+ * structure have not been updated yet.
+ */
+ rc = boot_update_security_counter(0, boot_img_hdr(&boot_data, 1));
+ if (rc != 0) {
+ BOOT_LOG_ERR("Security counter update failed after image upgrade.");
+ return rc;
+ }
+
/*
* 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
@@ -1399,6 +1451,25 @@
slot = 1;
reload_headers = true;
#ifndef MCUBOOT_OVERWRITE_ONLY
+ if (swap_type == BOOT_SWAP_TYPE_PERM) {
+ /* Update the stored security counter with the new image's security
+ * counter value (the one in the primary slot). Slot 0 holds the
+ * new image at this point, but slot 1's image header must be
+ * passed because the read 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(0, boot_img_hdr(&boot_data, 1));
+ if (rc != 0) {
+ BOOT_LOG_ERR("Security counter update failed after "
+ "image upgrade.");
+ goto out;
+ }
+ }
+
rc = boot_set_copy_done();
if (rc != 0) {
swap_type = BOOT_SWAP_TYPE_PANIC;
@@ -1459,6 +1530,24 @@
}
#endif /* MCUBOOT_VALIDATE_SLOT0 */
+ /* Update the stored security counter with the active image's security
+ * counter value. It will be updated only 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" swap can be performed if it's necessary.
+ */
+ if (swap_type == BOOT_SWAP_TYPE_NONE) {
+ rc = boot_update_security_counter(0, boot_img_hdr(&boot_data, 0));
+ if (rc != 0) {
+ BOOT_LOG_ERR("Security counter update failed after image "
+ "validation.");
+ goto out;
+ }
+ }
+
/* Always boot from the primary slot. */
rsp->br_flash_dev_id = boot_img_fa_device_id(&boot_data, 0);
rsp->br_image_off = boot_img_slot_off(&boot_data, 0);
@@ -1746,6 +1835,16 @@
/* The slot variable now refers to the newest image's slot in flash */
newest_image_header = boot_img_hdr(&boot_data, slot);
+ /* Update the security counter with the newest image's security
+ * counter value.
+ */
+ rc = boot_update_security_counter(slot, newest_image_header);
+ if (rc != 0) {
+ BOOT_LOG_ERR("Security counter update failed after image "
+ "validation.");
+ goto out;
+ }
+
#ifdef MCUBOOT_RAM_LOADING
if (newest_image_header->ih_flags & IMAGE_F_RAM_LOAD) {
/* Copy image to the load address from where it
@@ -1790,6 +1889,7 @@
} else {
/* No candidate image available */
rc = BOOT_EBADIMAGE;
+ goto out;
}
/* Save boot status to shared memory area */