diff --git a/boot/bootutil/include/bootutil/image.h b/boot/bootutil/include/bootutil/image.h index 15de3e01a..1d2c972c1 100644 --- a/boot/bootutil/include/bootutil/image.h +++ b/boot/bootutil/include/bootutil/image.h @@ -143,6 +143,10 @@ struct flash_area; */ #define IMAGE_TLV_ANY 0xffff /* Used to iterate over all TLV */ +#define VERSION_DEP_SLOT_ACTIVE 0x00 /* Check dependency against active slot. */ +#define VERSION_DEP_SLOT_PRIMARY 0x01 /* Check dependency against primary slot. */ +#define VERSION_DEP_SLOT_SECONDARY 0x02 /* Check dependency against secondary slot. */ + STRUCT_PACKED image_version { uint8_t iv_major; uint8_t iv_minor; @@ -152,7 +156,11 @@ STRUCT_PACKED image_version { struct image_dependency { uint8_t image_id; /* Image index (from 0) */ +#ifdef MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER + uint8_t slot; /* Image slot */ +#else uint8_t _pad1; +#endif /* MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER */ uint16_t _pad2; struct image_version image_min_version; /* Indicates at minimum which * version of firmware must be diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c index 8067f1ed6..e19d46a02 100644 --- a/boot/bootutil/src/loader.c +++ b/boot/bootutil/src/loader.c @@ -455,6 +455,24 @@ boot_verify_slot_dependency(struct boot_loader_state *state, uint8_t swap_type = state->swap_type[dep->image_id]; dep_slot = BOOT_IS_UPGRADE(swap_type) ? BOOT_SECONDARY_SLOT : BOOT_PRIMARY_SLOT; +#elif defined(MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER) + switch(dep->slot) { + case VERSION_DEP_SLOT_ACTIVE: + dep_slot = state->slot_usage[dep->image_id].active_slot; + break; + case VERSION_DEP_SLOT_PRIMARY: + dep_slot = BOOT_PRIMARY_SLOT; + break; + case VERSION_DEP_SLOT_SECONDARY: + dep_slot = BOOT_SECONDARY_SLOT; + break; + default: + return -1; + } + + if (!state->slot_usage[dep->image_id].slot_available[dep_slot]) { + return -1; + } #else dep_slot = state->slot_usage[dep->image_id].active_slot; #endif @@ -492,7 +510,27 @@ boot_verify_slot_dependency(struct boot_loader_state *state, } #endif - return rc; +#ifdef MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER + if (rc == 0) { + switch(dep->slot) { + case VERSION_DEP_SLOT_PRIMARY: + state->slot_usage[dep->image_id].slot_available[BOOT_PRIMARY_SLOT] = true; + state->slot_usage[dep->image_id].slot_available[BOOT_SECONDARY_SLOT] = false; + state->slot_usage[dep->image_id].active_slot = BOOT_PRIMARY_SLOT; + break; + case VERSION_DEP_SLOT_SECONDARY: + state->slot_usage[dep->image_id].slot_available[BOOT_PRIMARY_SLOT] = false; + state->slot_usage[dep->image_id].slot_available[BOOT_SECONDARY_SLOT] = true; + state->slot_usage[dep->image_id].active_slot = BOOT_SECONDARY_SLOT; + break; + case VERSION_DEP_SLOT_ACTIVE: + default: + break; + } + } +#endif /* MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER */ + +return rc; } #if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD) @@ -3399,6 +3437,119 @@ boot_select_or_erase(struct boot_loader_state *state) } #endif /* MCUBOOT_DIRECT_XIP && MCUBOOT_DIRECT_XIP_REVERT */ +#ifdef MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER +/** + * Tries to load a slot for all the images with validation. + * + * @param state Boot loader status information. + * + * @return 0 on success; nonzero on failure. + */ +fih_ret +boot_load_and_validate_images(struct boot_loader_state *state) +{ + uint32_t active_slot; + int rc; + fih_ret fih_rc; + uint32_t slot; + + /* Go over all the images and all slots and validate them */ + IMAGES_ITER(BOOT_CURR_IMG(state)) { + for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) { +#if BOOT_IMAGE_NUMBER > 1 + if (state->img_mask[BOOT_CURR_IMG(state)]) { + continue; + } +#endif + + /* Save the number of the active slot. */ + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = slot; + +#ifdef MCUBOOT_DIRECT_XIP + rc = boot_rom_address_check(state); + if (rc != 0) { + /* The image is placed in an unsuitable slot. */ + state->slot_usage[BOOT_CURR_IMG(state)].slot_available[slot] = false; + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = NO_ACTIVE_SLOT; + continue; + } + +#ifdef MCUBOOT_DIRECT_XIP_REVERT + rc = boot_select_or_erase(state); + if (rc != 0) { + /* The selected image slot has been erased. */ + state->slot_usage[BOOT_CURR_IMG(state)].slot_available[slot] = false; + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = NO_ACTIVE_SLOT; + continue; + } +#endif /* MCUBOOT_DIRECT_XIP_REVERT */ +#endif /* MCUBOOT_DIRECT_XIP */ + +#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); + if (rc != 0 ) { + /* Image cannot be ramloaded. */ + boot_remove_image_from_flash(state, slot); + state->slot_usage[BOOT_CURR_IMG(state)].slot_available[slot] = false; + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = NO_ACTIVE_SLOT; + continue; + } +#endif /* MCUBOOT_RAM_LOAD */ + + FIH_CALL(boot_validate_slot, fih_rc, state, slot, NULL, 0); + if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { + /* Image is invalid. */ +#ifdef MCUBOOT_RAM_LOAD + boot_remove_image_from_sram(state); +#endif /* MCUBOOT_RAM_LOAD */ + state->slot_usage[BOOT_CURR_IMG(state)].slot_available[slot] = false; + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = NO_ACTIVE_SLOT; + continue; + } + + /* Valid image loaded from a slot, go to the next slot. */ + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = NO_ACTIVE_SLOT; + } + } + + /* Go over all the images and all slots and validate them */ + IMAGES_ITER(BOOT_CURR_IMG(state)) { + /* All slots tried until a valid image found. Breaking from this loop + * means that a valid image found or already loaded. If no slot is + * found the function returns with error code. */ + while (true) { + /* Go over all the slots and try to load one */ + active_slot = state->slot_usage[BOOT_CURR_IMG(state)].active_slot; + if (active_slot != NO_ACTIVE_SLOT){ + /* A slot is already active, go to next image. */ + break; + } + + active_slot = find_slot_with_highest_version(state); + if (active_slot == NO_ACTIVE_SLOT) { + BOOT_LOG_INF("No slot to load for image %d", + BOOT_CURR_IMG(state)); + FIH_RET(FIH_FAILURE); + } + + /* Save the number of the active slot. */ + state->slot_usage[BOOT_CURR_IMG(state)].active_slot = active_slot; + + /* Valid image loaded from a slot, go to the next image. */ + break; + } + } + + FIH_RET(FIH_SUCCESS); +} + +#else /* MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER */ + /** * Tries to load a slot for all the images with validation. * @@ -3496,6 +3647,7 @@ boot_load_and_validate_images(struct boot_loader_state *state) FIH_RET(FIH_SUCCESS); } +#endif /* MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER */ /** * Updates the security counter for the current image. diff --git a/boot/zephyr/Kconfig b/boot/zephyr/Kconfig index caf02a261..85bf7a75b 100644 --- a/boot/zephyr/Kconfig +++ b/boot/zephyr/Kconfig @@ -937,6 +937,15 @@ config BOOT_VERSION_CMP_USE_BUILD_NUMBER minor and revision. Enable this option to take into account the build number as well. +config BOOT_VERSION_CMP_USE_SLOT_NUMBER + bool "Use slot number while comparing image version" + depends on (UPDATEABLE_IMAGE_NUMBER > 1) || BOOT_DIRECT_XIP || \ + BOOT_RAM_LOAD || MCUBOOT_DOWNGRADE_PREVENTION + help + By default, the image slot comparison relies only on active slot. + Enable this option to take into account the specified slot number + instead. + choice BOOT_DOWNGRADE_PREVENTION_CHOICE prompt "Downgrade prevention" optional diff --git a/boot/zephyr/include/mcuboot_config/mcuboot_config.h b/boot/zephyr/include/mcuboot_config/mcuboot_config.h index 9a29beaa6..b4e7c304d 100644 --- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h +++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h @@ -122,6 +122,10 @@ #define MCUBOOT_VERSION_CMP_USE_BUILD_NUMBER #endif +#ifdef CONFIG_BOOT_VERSION_CMP_USE_SLOT_NUMBER +#define MCUBOOT_VERSION_CMP_USE_SLOT_NUMBER +#endif + #ifdef CONFIG_BOOT_SWAP_SAVE_ENCTLV #define MCUBOOT_SWAP_SAVE_ENCTLV 1 #endif diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py index c7fb9e553..93acdafff 100644 --- a/scripts/imgtool/image.py +++ b/scripts/imgtool/image.py @@ -587,8 +587,9 @@ def create(self, key, public_key_format, enckey, dependencies=None, if dependencies is not None: for i in range(dependencies_num): payload = struct.pack( - e + 'B3x' + 'BBHI', + e + 'BB2x' + 'BBHI', int(dependencies[DEP_IMAGES_KEY][i]), + dependencies[DEP_VERSIONS_KEY][i].slot, dependencies[DEP_VERSIONS_KEY][i].major, dependencies[DEP_VERSIONS_KEY][i].minor, dependencies[DEP_VERSIONS_KEY][i].revision, diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py index 78f2e77ec..a2b0cce3d 100755 --- a/scripts/imgtool/main.py +++ b/scripts/imgtool/main.py @@ -27,6 +27,7 @@ import lzma import hashlib import base64 +from collections import namedtuple from imgtool import image, imgtool_version from imgtool.version import decode_version from imgtool.dumpinfo import dump_imginfo @@ -45,6 +46,8 @@ sys.exit("Python %s.%s or newer is required by imgtool." % MIN_PYTHON_VERSION) +SlottedSemiSemVersion = namedtuple('SemiSemVersion', ['major', 'minor', 'revision', + 'build', 'slot']) def gen_rsa2048(keyfile, passwd): keys.RSA.generate().export_private(path=keyfile, passwd=passwd) @@ -300,16 +303,33 @@ def get_dependencies(ctx, param, value): if len(images) == 0: raise click.BadParameter( "Image dependency format is invalid: {}".format(value)) - raw_versions = re.findall(r",\s*([0-9.+]+)\)", value) + raw_versions = re.findall(r",\s*(([0-9]+)\s*,)?\s*([0-9.+]+)\)", value) if len(images) != len(raw_versions): raise click.BadParameter( '''There's a mismatch between the number of dependency images and versions in: {}'''.format(value)) for raw_version in raw_versions: try: - versions.append(decode_version(raw_version)) + decoded_version = decode_version(raw_version[2]) + if len(raw_version[1]) > 0: + slotted_version = SlottedSemiSemVersion( + decoded_version.major, + decoded_version.minor, + decoded_version.revision, + decoded_version.build, + int(raw_version[1]) + ) + else: + slotted_version = SlottedSemiSemVersion( + decoded_version.major, + decoded_version.minor, + decoded_version.revision, + decoded_version.build, + 0 + ) except ValueError as e: raise click.BadParameter("{}".format(e)) + versions.append(slotted_version) dependencies = dict() dependencies[image.DEP_IMAGES_KEY] = images dependencies[image.DEP_VERSIONS_KEY] = versions @@ -404,7 +424,7 @@ def convert(self, value, param, ctx): '(for mcuboot <1.5)') @click.option('-d', '--dependencies', callback=get_dependencies, required=False, help='''Add dependence on another image, format: - "(,), ... "''') + "(,[],), ... "''') @click.option('-s', '--security-counter', callback=validate_security_counter, help='Specify the value of security counter. Use the `auto` ' 'keyword to automatically generate it from the image version.')