/* * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors * * SPDX-License-Identifier: BSD-3-Clause * */ #include #include "psa/crypto.h" #include "psa/error.h" #include "tfm_log_unpriv.h" #include "bootutil_priv.h" #include "bootutil/bootutil.h" #include "bootutil/image.h" #include "flash_map_backend/flash_map_backend.h" #include "sysflash/sysflash.h" #include "tfm_bootloader_fwu_abstraction.h" #include "tfm_boot_status.h" #include "service_api.h" #if (FWU_COMPONENT_NUMBER != MCUBOOT_IMAGE_NUMBER) #error "FWU_COMPONENT_NUMBER mismatch with MCUBOOT_IMAGE_NUMBER" #endif #define MAX_IMAGE_INFO_LENGTH (MCUBOOT_IMAGE_NUMBER * \ (sizeof(struct image_version) + \ SHARED_DATA_ENTRY_HEADER_SIZE)) /* * \struct fwu_image_info_data * * \brief Contains the received boot status information from bootloader * * \details This is a redefinition of \ref tfm_boot_data to allocate the * appropriate, service dependent size of \ref boot_data. */ typedef struct fwu_image_info_data_s { struct shared_data_tlv_header header; uint8_t data[MAX_IMAGE_INFO_LENGTH]; } fwu_image_info_data_t; typedef struct tfm_fwu_mcuboot_ctx_s { /* The flash area corresponding to component. */ const struct flash_area *fap; /* The size of the downloaded data in the FWU process. */ size_t loaded_size; } tfm_fwu_mcuboot_ctx_t; static tfm_fwu_mcuboot_ctx_t mcuboot_ctx[FWU_COMPONENT_NUMBER]; static fwu_image_info_data_t __attribute__((aligned(4))) boot_shared_data; static psa_status_t get_active_image_version(psa_fwu_component_t component, struct image_version *image_ver) { struct shared_data_tlv_entry tlv_entry; uint8_t *tlv_end; uint8_t *tlv_curr; /* The bootloader writes the image version information into the memory which * is shared between MCUboot and TF-M. Read the shared memory. */ if (boot_shared_data.header.tlv_magic != SHARED_DATA_TLV_INFO_MAGIC) { return PSA_ERROR_DATA_CORRUPT; } tlv_end = (uint8_t *)&boot_shared_data + boot_shared_data.header.tlv_tot_len; tlv_curr = boot_shared_data.data; while (tlv_curr < tlv_end) { (void)memcpy(&tlv_entry, tlv_curr, SHARED_DATA_ENTRY_HEADER_SIZE); if ((GET_FWU_CLAIM(tlv_entry.tlv_type) == SW_VERSION) && (GET_FWU_MODULE(tlv_entry.tlv_type) == component)) { if (tlv_entry.tlv_len != sizeof(struct image_version)) { return PSA_ERROR_DATA_CORRUPT; } memcpy(image_ver, tlv_curr + SHARED_DATA_ENTRY_HEADER_SIZE, tlv_entry.tlv_len); return PSA_SUCCESS; } tlv_curr += SHARED_DATA_ENTRY_HEADER_SIZE + tlv_entry.tlv_len; } return PSA_ERROR_DATA_CORRUPT; } psa_status_t fwu_bootloader_init(void) { psa_status_t ret; /* Get shared bootloader data */ ret = tfm_core_get_boot_data(TLV_MAJOR_FWU, (struct tfm_boot_data *)&boot_shared_data, sizeof(boot_shared_data)); if (ret != PSA_SUCCESS) { return PSA_ERROR_STORAGE_FAILURE; } /* add Init of specific flash driver */ flash_area_driver_init(); return PSA_SUCCESS; } psa_status_t fwu_bootloader_staging_area_init(psa_fwu_component_t component, const void *manifest, size_t manifest_size) { const struct flash_area *fap; /* MCUboot uses bundled manifest. */ if ((manifest_size != 0) || (component >= FWU_COMPONENT_NUMBER)) { return PSA_ERROR_INVALID_ARGUMENT; } if (flash_area_open(FLASH_AREA_IMAGE_SECONDARY(component), &fap) != 0) { ERROR_UNPRIV_RAW("TFM FWU: opening flash failed.\n"); return PSA_ERROR_STORAGE_FAILURE; } if (flash_area_erase(fap, 0, fap->fa_size) != 0) { ERROR_UNPRIV_RAW("TFM FWU: erasing flash failed.\n"); return PSA_ERROR_GENERIC_ERROR; } mcuboot_ctx[component].fap = fap; /* Reset the loaded_size. */ mcuboot_ctx[component].loaded_size = 0; return PSA_SUCCESS; } psa_status_t fwu_bootloader_load_image(psa_fwu_component_t component, size_t image_offset, const void *block, size_t block_size) { const struct flash_area *fap; if ((block == NULL) || (component >= FWU_COMPONENT_NUMBER)) { return PSA_ERROR_INVALID_ARGUMENT; } /* The component should already be added into the mcuboot_ctx. */ if (mcuboot_ctx[component].fap != NULL) { fap = mcuboot_ctx[component].fap; } else { return PSA_ERROR_BAD_STATE; } if (flash_area_write(fap, image_offset, block, block_size) != 0) { ERROR_UNPRIV_RAW("TFM FWU: write flash failed.\n"); return PSA_ERROR_STORAGE_FAILURE; } /* The overflow check has been done in flash_area_write. */ mcuboot_ctx[component].loaded_size += block_size; return PSA_SUCCESS; } #if (MCUBOOT_IMAGE_NUMBER > 1) /** * \brief Compare image version numbers not including the build number. * * \param[in] image_ver_1 The first image version to compare. * * \param[in] image_ver_2 The second image version to compare. * * \return true image_ver_1 is greater or equal than image_ver_2. * \return false image_ver_1 is less than image_ver_2. */ static bool is_version_greater_or_equal(const struct image_version *image_ver_1, const struct image_version *image_ver_2) { if (image_ver_1->iv_major > image_ver_2->iv_major) { return true; } if (image_ver_1->iv_major < image_ver_2->iv_major) { return false; } /* The major version numbers are equal, continue comparison. */ if (image_ver_1->iv_minor > image_ver_2->iv_minor) { return true; } if (image_ver_1->iv_minor < image_ver_2->iv_minor) { return false; } /* The minor version numbers are equal, continue comparison. */ if (image_ver_1->iv_revision >= image_ver_2->iv_revision) { return true; } return false; } #endif psa_status_t fwu_bootloader_install_image(const psa_fwu_component_t *candidates, uint8_t number) { uint8_t index_i, cand_index; #if (MCUBOOT_IMAGE_NUMBER > 1) psa_fwu_component_t component; const struct flash_area *fap; struct image_tlv_iter it; struct image_header hdr; int rc; uint32_t off; uint16_t len; struct image_dependency dep; struct image_version image_ver = { 0 }; const struct flash_area *fap_secondary; struct image_header hdr_secondary; bool check_pass = true; #endif if (candidates == NULL) { return PSA_ERROR_INVALID_ARGUMENT; } #if (MCUBOOT_IMAGE_NUMBER > 1) for (cand_index = 0; cand_index < number; cand_index++) { component = candidates[cand_index]; /* The image should already be added into the mcuboot_ctx. */ if ((component >= FWU_COMPONENT_NUMBER) || (mcuboot_ctx[component].fap == NULL)) { return PSA_ERROR_INVALID_ARGUMENT; } fap = mcuboot_ctx[component].fap; /* Read the image header. */ if (flash_area_read(fap, 0, &hdr, sizeof(hdr)) != 0) { return PSA_ERROR_STORAGE_FAILURE; } /* Return PSA_ERROR_DATA_CORRUPT if the image header is invalid. */ if (hdr.ih_magic != IMAGE_MAGIC) { return PSA_ERROR_DATA_CORRUPT; } /* Initialize the iterator. */ if (bootutil_tlv_iter_begin(&it, &hdr, fap, IMAGE_TLV_DEPENDENCY, true)) { return PSA_ERROR_STORAGE_FAILURE; } /* Check dependencies. */ while (true) { rc = bootutil_tlv_iter_next(&it, &off, &len, NULL); if (rc < 0) { return PSA_ERROR_STORAGE_FAILURE; } else if (rc > 0) { /* No more dependency found. */ rc = 0; break; } /* Check against payload length overflow */ if (len > sizeof(dep)) { return PSA_ERROR_INVALID_ARGUMENT; } /* A dependency requirement is found. Set check_pass to false. */ check_pass = false; if (flash_area_read(fap, off, &dep, len) != 0) { return PSA_ERROR_STORAGE_FAILURE; } if (dep.image_id > MCUBOOT_IMAGE_NUMBER) { return PSA_ERROR_DATA_CORRUPT; } /* As this partition does not validate the image in the secondary slot, * so it has no information of which image will be chosen to run after * reboot. So if the dependency image in the primary slot or that in the * secondary slot can meet the dependency requirement, then the * dependency check pass. */ /* Check the dependency image in the primary slot. */ if (get_active_image_version(dep.image_id, &image_ver) != PSA_SUCCESS) { return PSA_ERROR_STORAGE_FAILURE; } /* Check whether the version of the running image can meet the * dependency requirement. */ if (is_version_greater_or_equal(&image_ver, &dep.image_min_version)) { check_pass = true; } else { /* Check whether the CANDIDATE image can meet this image's dependency * requirement. */ for (index_i = 0; index_i < number; index_i++) { if (candidates[index_i] == dep.image_id) break; } if ((index_i < number) && (mcuboot_ctx[dep.image_id].fap != NULL)) { /* The running image cannot meet the dependency requirement. Check * the dependency image in the secondary slot. */ fap_secondary = mcuboot_ctx[dep.image_id].fap; if (flash_area_read(fap_secondary, 0, &hdr_secondary, sizeof(hdr_secondary)) != 0) { return PSA_ERROR_STORAGE_FAILURE; } /* Check the version of the dependency image in the secondary slot * only if the image header is good. */ if ((hdr_secondary.ih_magic == IMAGE_MAGIC) && (is_version_greater_or_equal(&hdr_secondary.ih_ver, &dep.image_min_version))) { /* The dependency image in the secondary slot meet the * dependency requirement. */ check_pass = true; } } } /* Return directly if dependency check fails. */ if (!check_pass) { return PSA_ERROR_DEPENDENCY_NEEDED; } } } #endif /* Write the boot magic in image trailer so that these images will be * taken as candidates. */ for (cand_index = 0; cand_index < number; cand_index++) { if (boot_set_pending_multi(candidates[cand_index], false) != 0) { /* If failure happens, reject candidates have been installed successfully. */ for (index_i = 0; index_i < cand_index; index_i++) { if (fwu_bootloader_reject_staged_image(candidates[index_i]) != PSA_SUCCESS) { break; } } return PSA_ERROR_STORAGE_FAILURE; } } return PSA_SUCCESS_REBOOT; } static inline uint32_t boot_magic_off(const struct flash_area *fap) { return flash_area_get_size(fap) - BOOT_MAGIC_SZ; } #if (defined(MCUBOOT_DIRECT_XIP) && defined(MCUBOOT_DIRECT_XIP_REVERT)) || \ defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE) static inline uint32_t boot_image_ok_off(const struct flash_area *fap) { return ALIGN_DOWN(boot_magic_off(fap) - BOOT_MAX_ALIGN, BOOT_MAX_ALIGN); } static psa_status_t erase_image_ok(const struct flash_area *fap) { uint32_t off; uint8_t buf[BOOT_MAX_ALIGN]; uint8_t erased_val; uint32_t align; /* off of image OK flag is already BOOT_MAX_ALIGN aligned. */ off = boot_image_ok_off(fap); /* Clear the image ok trailer. */ align = flash_area_align(fap); align = ALIGN_UP(BOOT_MAX_ALIGN, align); if (align > BOOT_MAX_ALIGN) { return PSA_ERROR_STORAGE_FAILURE; } erased_val = flash_area_erased_val(fap); memset(buf, erased_val, align); if (flash_area_write(fap, off, buf, align) != 0) { return PSA_ERROR_STORAGE_FAILURE; } return PSA_SUCCESS; } #endif psa_status_t fwu_bootloader_mark_image_accepted(const psa_fwu_component_t *trials, uint8_t number) { /* As RAM_LOAD and OVERWRITE_ONLY do not support image revert, the image * does not need to be confirmed explicitly in these two upgrade strategies. * Image revert is supported in SWAP upgrade strategy and DIRECT_XIP upgrade * strategy when MCUBOOT_DIRECT_XIP_REVERT is true. In these cases, the * image needs to be set as a permanent image explicitly. Then the accepted * image can still be selected as the running image during next time reboot * up. Otherwise, the image will be reverted and the previous one will be * chosen as the running image. */ #if (defined(MCUBOOT_DIRECT_XIP) && defined(MCUBOOT_DIRECT_XIP_REVERT)) || \ defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE) uint8_t trial_index, i; psa_fwu_component_t component; const struct flash_area *fap; if (trials == NULL) { return PSA_ERROR_INVALID_ARGUMENT; } for (trial_index = 0; trial_index < number; trial_index++) { component = trials[trial_index]; if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } if (flash_area_open(FLASH_AREA_IMAGE_SECONDARY(component), &fap) != 0) { return PSA_ERROR_STORAGE_FAILURE; } if (fap == NULL) { return PSA_ERROR_INVALID_ARGUMENT; } mcuboot_ctx[component].fap = fap; } for (trial_index = 0; trial_index < number; trial_index++) { component = trials[trial_index]; if (boot_set_confirmed_multi(component) != 0) { for (i = 0; i < trial_index; i++) { if (erase_image_ok(mcuboot_ctx[component].fap) != 0) { break; } } return PSA_ERROR_STORAGE_FAILURE; } } #else (void)trials; (void)number; #endif return PSA_SUCCESS; } static psa_status_t erase_boot_magic(const struct flash_area *fap) { uint32_t off, pad_off; uint8_t magic[BOOT_MAGIC_ALIGN_SIZE]; uint8_t erased_val; off = boot_magic_off(fap); pad_off = ALIGN_DOWN(off, BOOT_MAX_ALIGN); erased_val = flash_area_erased_val(fap); memset(&magic[0], erased_val, sizeof(magic)); /* Clear the boot magic trailer. */ if (flash_area_write(fap, pad_off, &magic[0], BOOT_MAGIC_ALIGN_SIZE)) { return PSA_ERROR_STORAGE_FAILURE; } return PSA_SUCCESS; } /* Reject the staged image. */ psa_status_t fwu_bootloader_reject_staged_image(psa_fwu_component_t component) { if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } /* The image should already be added into the mcuboot_ctx. */ if (mcuboot_ctx[component].fap != NULL) { return erase_boot_magic(mcuboot_ctx[component].fap); } /* The component is not in FWU process. */ return PSA_ERROR_DOES_NOT_EXIST; } /* Reject the running image in trial state. */ psa_status_t fwu_bootloader_reject_trial_image(psa_fwu_component_t component) { if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } /* The image will be reverted if it is not accepted explicitly. */ return PSA_SUCCESS_REBOOT; } psa_status_t fwu_bootloader_abort(psa_fwu_component_t component) { const struct flash_area *fap; if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } /* The image should already be added into the mcuboot_ctx. */ if (mcuboot_ctx[component].fap != NULL) { fap = mcuboot_ctx[component].fap; } else { return PSA_ERROR_INVALID_ARGUMENT; } flash_area_erase(fap, 0, fap->fa_size); flash_area_close(fap); mcuboot_ctx[component].fap = NULL; mcuboot_ctx[component].loaded_size = 0; return PSA_SUCCESS; } static psa_status_t util_img_hash(const struct flash_area *fap, size_t data_size, uint8_t *hash_result, size_t buf_size, size_t *hash_size) { psa_hash_operation_t handle = psa_hash_operation_init(); psa_status_t status; uint8_t tmpbuf[BOOT_TMPBUF_SZ]; uint32_t tmp_buf_sz = BOOT_TMPBUF_SZ; uint32_t blk_sz = 0; uint32_t off; /* Setup the hash object for the desired hash. */ status = psa_hash_setup(&handle, PSA_ALG_SHA_256); if (status != PSA_SUCCESS) { return status; } for (off = 0; off < data_size; off += blk_sz) { blk_sz = data_size - off; if (blk_sz > tmp_buf_sz) { blk_sz = tmp_buf_sz; } if (flash_area_read(fap, off, tmpbuf, blk_sz)) { return PSA_ERROR_STORAGE_FAILURE; } status = psa_hash_update(&handle, tmpbuf, blk_sz); if (status != PSA_SUCCESS) { return status; } } status = psa_hash_finish(&handle, hash_result, buf_size, hash_size); return status; } static psa_status_t get_second_image_digest(psa_fwu_component_t component, psa_fwu_component_info_t *info) { const struct flash_area *fap = NULL; uint8_t hash[TFM_FWU_MAX_DIGEST_SIZE] = {0}; size_t hash_size = 0; psa_status_t ret = PSA_SUCCESS; size_t data_size; if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } /* Check if the image is in a FWU process. */ if (mcuboot_ctx[component].fap != NULL) { /* Calculate hash on the downloaded data. */ data_size = mcuboot_ctx[component].loaded_size; } else { return PSA_ERROR_INVALID_ARGUMENT; } if ((flash_area_open(FLASH_AREA_IMAGE_SECONDARY(component), &fap)) != 0) { ERROR_UNPRIV_RAW("TFM FWU: opening flash failed.\n"); return PSA_ERROR_STORAGE_FAILURE; } if (util_img_hash(fap, data_size, hash, (size_t)TFM_FWU_MAX_DIGEST_SIZE, &hash_size) == PSA_SUCCESS) { memcpy(info->impl.candidate_digest, hash, hash_size); } else { ret = PSA_ERROR_STORAGE_FAILURE; } flash_area_close(fap); return ret; } psa_status_t fwu_bootloader_get_image_info(psa_fwu_component_t component, bool query_state, bool query_impl_info, psa_fwu_component_info_t *info) { #if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD) && \ !defined(MCUBOOT_OVERWRITE_ONLY) uint8_t image_ok = BOOT_FLAG_UNSET; #endif const struct flash_area *fap = NULL; struct image_version image_version; psa_status_t ret = PSA_SUCCESS; if (info == NULL) { return PSA_ERROR_INVALID_ARGUMENT; } if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } if ((flash_area_open(FLASH_AREA_IMAGE_PRIMARY(component), &fap)) != 0) { ERROR_UNPRIV_RAW("TFM FWU: opening flash failed.\n"); return PSA_ERROR_STORAGE_FAILURE; } info->max_size = fap->fa_size; info->location = fap->fa_id; info->flags = PSA_FWU_FLAG_VOLATILE_STAGING; if (query_state) { /* As DIRECT_XIP, RAM_LOAD and OVERWRITE_ONLY do not support image revert. * So the running image is in INSTALLED state in these three upgrade * strategies. In the SWAP case, the image_ok flag should be read to check * whether the running image has been confirmed as a pernament image. */ #if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD) && \ !defined(MCUBOOT_OVERWRITE_ONLY) /* Get value of image-ok flag of the image to check whether application * itself is already confirmed. */ if (boot_read_image_ok(fap, &image_ok) != 0) { ret = PSA_ERROR_STORAGE_FAILURE; goto close_return; } if (image_ok == BOOT_FLAG_SET) { info->state = PSA_FWU_READY; } else { info->state = PSA_FWU_TRIAL; } #else info->state = PSA_FWU_READY; #endif } if (get_active_image_version(component, &image_version) == PSA_SUCCESS) { info->version.major = image_version.iv_major; info->version.minor = image_version.iv_minor; info->version.patch = image_version.iv_revision; info->version.build = image_version.iv_build_num; } else { ret = PSA_ERROR_STORAGE_FAILURE; goto close_return; } if (query_impl_info) { ret = get_second_image_digest(component, info); } close_return: flash_area_close(fap); return ret; } psa_status_t fwu_bootloader_clean_component(psa_fwu_component_t component) { const struct flash_area *fap = NULL; if (component >= FWU_COMPONENT_NUMBER) { return PSA_ERROR_INVALID_ARGUMENT; } /* Check if the image is in a FWU process. */ if (mcuboot_ctx[component].fap != NULL) { fap = mcuboot_ctx[component].fap; if (flash_area_erase(fap, 0, fap->fa_size) != 0) { return PSA_ERROR_STORAGE_FAILURE; } mcuboot_ctx[component].fap = NULL; } else { return PSA_ERROR_DOES_NOT_EXIST; } return PSA_SUCCESS; }