Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions boot/bootutil/include/bootutil/boot_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include "bootutil/bootutil.h"
#include "bootutil/fault_injection_hardening.h"
#include <limits.h>

#define DO_HOOK_CALL(f, ret_default, ...) \
f(__VA_ARGS__)
Expand Down Expand Up @@ -284,4 +285,84 @@ int flash_area_get_device_id_hook(const struct flash_area *fa,
*/
int boot_find_next_slot_hook(struct boot_loader_state *state, uint8_t image, enum boot_slot *active_slot);

/* Swap-status hook family. Uses the same DO_HOOK_CALL / HOOK_CALL_NOP dispatch
* machinery as the other hook families (image-access, boot-go, find-slot, ...):
* when MCUBOOT_SWAP_STATUS_HOOKS is enabled BOOT_STATUS_HOOK_CALL invokes the
* hook and yields its return value; when disabled it evaluates to ret_default
* with no call (and no link dependency on the hook symbol).
*
* Semantics are REPLACE: when the payload is compiled in it fully owns status
* bookkeeping, so a hook, once present, always handles the call -- it never
* defers to the built-in path. Callers use the upstream idiom:
*
* int rc = BOOT_STATUS_HOOK_CALL(f, BOOT_STATUS_HOOK_REGULAR, ...);
* if (rc != BOOT_STATUS_HOOK_REGULAR) {
* return rc; // hook owned it: success or a real error
* }
* ... built-in implementation ...
*
* The amend variant (built-in runs first, hook augments at a defined point)
* passes ret_default = 0, i.e. "nothing to amend" when the family is disabled:
*
* rc = BOOT_STATUS_HOOK_CALL(f, 0, ...);
*
* BOOT_STATUS_HOOK_REGULAR is the family's "not handled" sentinel. It is NOT
* BOOT_HOOK_REGULAR: BOOT_HOOK_REGULAR == BOOT_EFLASH == 1 (bootutil_public.h)
* and these hooks return BOOT_EFLASH on real flash errors, so reusing it would
* misread a flash error as "defer to the built-in status-byte path" and run it
* after a flash fault. INT_MIN is distinct from 0 and every BOOT_E* code, so no
* hook return value can collide with it. */
#define BOOT_STATUS_HOOK_REGULAR INT_MIN

#ifdef MCUBOOT_SWAP_STATUS_HOOKS
#define BOOT_STATUS_HOOK_CALL(f, ret_default, ...) \
DO_HOOK_CALL(f, ret_default, __VA_ARGS__)
#else
#define BOOT_STATUS_HOOK_CALL(f, ret_default, ...) \
HOOK_CALL_NOP(f, ret_default, __VA_ARGS__)
#endif

/* boot_hooks.h is a PUBLIC header with consumers that never include the private
* bootutil_priv.h (e.g. boot/zephyr/flash_map_extended.c) -- struct boot_status
* is defined only there, so forward-declare it or the prototypes below create
* prototype-scope tags (warning / incompatible-declaration risk). */
struct boot_status;

/**
* Swap-status hook implementations are strong symbols that MUST be linked
* whenever MCUBOOT_SWAP_STATUS_HOOKS is defined.
*
* @return 0 on success, or a BOOT_E* error which the upstream caller returns
* verbatim. BOOT_HOOK_REGULAR has no meaning for this family because
* there is no runtime fallthrough.
*/
int boot_write_status_hook(const struct boot_loader_state *state,
struct boot_status *bs);
int swap_status_init_hook(struct boot_loader_state *state,
const struct flash_area *fap,
const struct boot_status *bs);
int swap_read_status_bytes_hook(const struct flash_area *fap,
struct boot_loader_state *state,
struct boot_status *bs);

/**
* Commits primary trailer authority before the SCRATCH area is erased.
*
* @return 0 on success, or a BOOT_E* error. The caller must not erase the
* SCRATCH area after an error.
*/
int swap_status_before_scratch_erase_hook(const struct boot_loader_state *state,
const struct flash_area *fap_pri,
const struct flash_area *fap_scratch,
struct boot_status *bs);
Comment on lines +354 to +357

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the plan is to deprecate swap using scratch, seems a bit superfluous?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap scratch is used in Infineon projects. Any chance to preserve that hooks?


/**
* Amends boot_swap_image after swap_run(): finalizes the swap when the overlay
* tracks progress out-of-band.
*
* @return 0 on success, or a BOOT_E* error which the caller asserts on.
*/
int boot_swap_complete_hook(struct boot_loader_state *state,
struct boot_status *bs);

#endif /*H_BOOTUTIL_HOOKS*/
1 change: 1 addition & 0 deletions boot/bootutil/src/bootutil_misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2023 Nordic Semiconductor ASA
* Copyright (c) 2026 Infineon Technologies AG
*
*/
#ifndef H_BOOTUTIL_MISC_
Expand Down
16 changes: 16 additions & 0 deletions boot/bootutil/src/bootutil_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Copyright (c) 2017-2020 Linaro LTD
* Copyright (c) 2017-2019 JUUL Labs
* Copyright (c) 2019-2021 Arm Limited
* Copyright (c) 2026 Infineon Technologies AG
*
* Original license:
*
Expand Down Expand Up @@ -344,6 +345,21 @@ int boot_slots_compatible(struct boot_loader_state *state);
uint32_t boot_status_internal_off(const struct boot_status *bs, int elem_sz);
int boot_read_image_header(struct boot_loader_state *state, int slot,
struct image_header *out_hdr, struct boot_status *bs);
#ifdef MCUBOOT_ENC_IMAGES
/*
* Apply, in place, the exact content transform boot_copy_region applies to one
* chunk being written to `fap_dst` at absolute offset `abs_off` within the
* destination image area (not a device address). The primitive recomputes
* encrypted_src / encrypted_dst / only_copy / source_slot from `state`,
* `fap_src`, and `fap_dst` and selects the governing header itself -- primary
* header when encrypting, secondary header when decrypting. No-op for a
* non-encrypted image or a same-slot (only_copy) move.
*/
void boot_transform_chunk(struct boot_loader_state *state,
const struct flash_area *fap_src,
const struct flash_area *fap_dst,
uint32_t abs_off, uint8_t *buf, uint32_t chunk_sz);
#endif
#if defined(MCUBOOT_SWAP_USING_OFFSET) && defined(MCUBOOT_ENC_IMAGES)
int boot_copy_region(struct boot_loader_state *state,
const struct flash_area *fap_src,
Expand Down
193 changes: 109 additions & 84 deletions boot/bootutil/src/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Copyright (c) 2016-2019 JUUL Labs
* Copyright (c) 2019-2023 Arm Limited
* Copyright (c) 2024-2025 Nordic Semiconductor ASA
* Copyright (c) 2026 Infineon Technologies AG
*
* Original license:
*
Expand Down Expand Up @@ -413,6 +414,11 @@ boot_status_is_reset(const struct boot_status *bs)
int
boot_write_status(const struct boot_loader_state *state, struct boot_status *bs)
{
int hook_rc = BOOT_STATUS_HOOK_CALL(boot_write_status_hook,
BOOT_STATUS_HOOK_REGULAR, state, bs);
if (hook_rc != BOOT_STATUS_HOOK_REGULAR) {
return hook_rc;
}
const struct flash_area *fap;
uint32_t off;
int rc = 0;
Expand Down Expand Up @@ -754,40 +760,13 @@ boot_validated_swap_type(struct boot_loader_state *state,

#if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD)

/**
* Copies the contents of one flash region to another. You must erase the
* destination region prior to calling this function.
*
* @param flash_area_id_src The ID of the source flash area.
* @param flash_area_id_dst The ID of the destination flash area.
* @param off_src The offset within the source flash area to
* copy from.
* @param off_dst The offset within the destination flash area to
* copy to.
* @param sz The number of bytes to copy.
* @param sector_off (Swap using offset with encryption only) the
* sector offset for encryption/decryption
*
* @return 0 on success; nonzero on failure.
*/
int
#if defined(MCUBOOT_SWAP_USING_OFFSET) && defined(MCUBOOT_ENC_IMAGES)
boot_copy_region(struct boot_loader_state *state,
const struct flash_area *fap_src,
const struct flash_area *fap_dst,
uint32_t off_src, uint32_t off_dst, uint32_t sz, uint32_t sector_off)
#else
boot_copy_region(struct boot_loader_state *state,
const struct flash_area *fap_src,
const struct flash_area *fap_dst,
uint32_t off_src, uint32_t off_dst, uint32_t sz)
#endif
{
uint32_t bytes_copied;
int chunk_sz;
int rc;
#ifdef MCUBOOT_ENC_IMAGES
uint32_t off = off_dst;
void
boot_transform_chunk(struct boot_loader_state *state,
const struct flash_area *fap_src,
const struct flash_area *fap_dst,
uint32_t abs_off, uint8_t *buf, uint32_t chunk_sz)
{
uint32_t tlv_off;
size_t blk_off;
struct image_header *hdr;
Expand All @@ -805,13 +784,7 @@ boot_copy_region(struct boot_loader_state *state,
/* In case of encryption enabled, we may have to do more work than
* just copy bytes */
bool only_copy = false;
#else
(void)state;
#endif

TARGET_STATIC uint8_t buf[BUF_SZ] __attribute__((aligned(4)));

#ifdef MCUBOOT_ENC_IMAGES
encrypted_src = (flash_area_get_id(fap_src) != FLASH_AREA_IMAGE_PRIMARY(image_index));
encrypted_dst = (flash_area_get_id(fap_dst) != FLASH_AREA_IMAGE_PRIMARY(image_index));

Expand All @@ -831,8 +804,92 @@ boot_copy_region(struct boot_loader_state *state,
*/
only_copy = true;
}

/* If only copy, then does not matter if header indicates need for
* encryption/decryption, we just copy data. */
if (!only_copy && IS_ENCRYPTED(hdr)) {
if (abs_off < hdr->ih_hdr_size) {
/* do not decrypt header */
if (abs_off + chunk_sz > hdr->ih_hdr_size) {
/* The lower part of the chunk contains header data */
blk_off = 0;
blk_sz = chunk_sz - (hdr->ih_hdr_size - abs_off);
idx = hdr->ih_hdr_size - abs_off;
} else {
/* The chunk contains exclusively header data */
blk_sz = 0; /* nothing to decrypt */
}
} else {
idx = 0;
blk_sz = chunk_sz;
blk_off = (abs_off - hdr->ih_hdr_size) & 0xf;
}

if (blk_sz > 0)
{
tlv_off = BOOT_TLV_OFF(hdr);
if (abs_off + chunk_sz > tlv_off) {
/* do not decrypt TLVs */
if (abs_off >= tlv_off) {
blk_sz = 0;
} else {
blk_sz = tlv_off - abs_off - idx;
}
}
if (source_slot == 0) {
boot_enc_encrypt(BOOT_CURR_ENC_SLOT(state, source_slot),
(abs_off + idx) - hdr->ih_hdr_size, blk_sz,
blk_off, &buf[idx]);
} else {
boot_enc_decrypt(BOOT_CURR_ENC_SLOT(state, source_slot),
(abs_off + idx) - hdr->ih_hdr_size, blk_sz,
blk_off, &buf[idx]);
}
}
}
}
#endif

/**
* Copies the contents of one flash region to another. You must erase the
* destination region prior to calling this function.
*
* @param flash_area_id_src The ID of the source flash area.
* @param flash_area_id_dst The ID of the destination flash area.
* @param off_src The offset within the source flash area to
* copy from.
* @param off_dst The offset within the destination flash area to
* copy to.
* @param sz The number of bytes to copy.
* @param sector_off (Swap using offset with encryption only) the
* sector offset for encryption/decryption
*
* @return 0 on success; nonzero on failure.
*/
int
#if defined(MCUBOOT_SWAP_USING_OFFSET) && defined(MCUBOOT_ENC_IMAGES)
boot_copy_region(struct boot_loader_state *state,
const struct flash_area *fap_src,
const struct flash_area *fap_dst,
uint32_t off_src, uint32_t off_dst, uint32_t sz, uint32_t sector_off)
#else
boot_copy_region(struct boot_loader_state *state,
const struct flash_area *fap_src,
const struct flash_area *fap_dst,
uint32_t off_src, uint32_t off_dst, uint32_t sz)
#endif
{
uint32_t bytes_copied;
int chunk_sz;
int rc;
#ifdef MCUBOOT_ENC_IMAGES
uint32_t off = off_dst;
#else
(void)state;
#endif

TARGET_STATIC uint8_t buf[BUF_SZ] __attribute__((aligned(4)));

bytes_copied = 0;
while (bytes_copied < sz) {
if (sz - bytes_copied > sizeof buf) {
Expand All @@ -847,53 +904,12 @@ boot_copy_region(struct boot_loader_state *state,
}

#ifdef MCUBOOT_ENC_IMAGES
/* If only copy, then does not matter if header indicates need for
* encryption/decryption, we just copy data. */
if (!only_copy && IS_ENCRYPTED(hdr)) {
#if defined(MCUBOOT_SWAP_USING_OFFSET)
uint32_t abs_off = off - sector_off + bytes_copied;
uint32_t abs_off = off - sector_off + bytes_copied;
#else
uint32_t abs_off = off + bytes_copied;
#endif
if (abs_off < hdr->ih_hdr_size) {
/* do not decrypt header */
if (abs_off + chunk_sz > hdr->ih_hdr_size) {
/* The lower part of the chunk contains header data */
blk_off = 0;
blk_sz = chunk_sz - (hdr->ih_hdr_size - abs_off);
idx = hdr->ih_hdr_size - abs_off;
} else {
/* The chunk contains exclusively header data */
blk_sz = 0; /* nothing to decrypt */
}
} else {
idx = 0;
blk_sz = chunk_sz;
blk_off = (abs_off - hdr->ih_hdr_size) & 0xf;
}

if (blk_sz > 0)
{
tlv_off = BOOT_TLV_OFF(hdr);
if (abs_off + chunk_sz > tlv_off) {
/* do not decrypt TLVs */
if (abs_off >= tlv_off) {
blk_sz = 0;
} else {
blk_sz = tlv_off - abs_off - idx;
}
}
if (source_slot == 0) {
boot_enc_encrypt(BOOT_CURR_ENC_SLOT(state, source_slot),
(abs_off + idx) - hdr->ih_hdr_size, blk_sz,
blk_off, &buf[idx]);
} else {
boot_enc_decrypt(BOOT_CURR_ENC_SLOT(state, source_slot),
(abs_off + idx) - hdr->ih_hdr_size, blk_sz,
blk_off, &buf[idx]);
}
}
}
uint32_t abs_off = off + bytes_copied;
#endif
boot_transform_chunk(state, fap_src, fap_dst, abs_off, buf, chunk_sz);
#endif

rc = flash_area_write(fap_dst, off_dst + bytes_copied, buf, chunk_sz);
Expand Down Expand Up @@ -1193,6 +1209,15 @@ boot_swap_image(struct boot_loader_state *state, struct boot_status *bs)

swap_run(state, bs, copy_size);

/* Post-swap finalization. When the overlay tracks progress out-of-band
* this reconstructs any primary trailer field left uncommitted by an
* interrupted swap and erases a stale scratch trailer.
* BOOT_STATUS_HOOK_CALL evaluates to its ret_default (0, no-op)
* when the status-hook overlay is not compiled in,
* so builds without a swap-state-hook provider are unaffected. */
rc = BOOT_STATUS_HOOK_CALL(boot_swap_complete_hook, 0, state, bs);
assert(rc == 0);

#ifdef MCUBOOT_VALIDATE_PRIMARY_SLOT
extern int boot_status_fails;
if (boot_status_fails > 0) {
Expand Down
Loading
Loading