blob: 897ac71e6256727ba61bee4cc68eb76066c6b98f [file] [log] [blame] [edit]
/*
* SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <string.h>
#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;
}