Skip to content

Commit d552e7b

Browse files
committed
boot: Add MCUboot manifest TLV
Add a possibility to attach a basic manifest with expected digests to an image. Alter the image verification logic, so only digests specified by the manifest are allowed on the device. Signed-off-by: Tomasz Chyrowicz <[email protected]>
1 parent 7d0adc2 commit d552e7b

File tree

12 files changed

+280
-1
lines changed

12 files changed

+280
-1
lines changed

boot/bootutil/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ target_sources(bootutil
3232
src/image_ed25519.c
3333
src/image_rsa.c
3434
src/image_validate.c
35+
src/mcuboot_manifest.c
3536
src/loader.c
3637
src/swap_misc.c
3738
src/swap_move.c

boot/bootutil/include/bootutil/image.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ extern "C" {
134134
#define IMAGE_TLV_COMP_DEC_SIZE 0x73 /* Compressed decrypted image size */
135135
#define IMAGE_TLV_UUID_VID 0x74 /* Vendor unique identifier */
136136
#define IMAGE_TLV_UUID_CID 0x75 /* Device class unique identifier */
137+
#define IMAGE_TLV_MANIFEST 0x76 /* Transaction manifest */
137138
/*
138139
* vendor reserved TLVs at xxA0-xxFF,
139140
* where xx denotes the upper byte
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef __MCUBOOT_MANIFEST_H__
8+
#define __MCUBOOT_MANIFEST_H__
9+
10+
/**
11+
* @file mcuboot_manifest.h
12+
*
13+
* @note This file is only used when MCUBOOT_MANIFEST_UPDATES is enabled.
14+
*/
15+
16+
#include <stdint.h>
17+
#include "bootutil/bootutil.h"
18+
#include "bootutil/crypto/sha.h"
19+
20+
#ifndef __packed
21+
#define __packed __attribute__((__packed__))
22+
#endif
23+
24+
#ifdef __cplusplus
25+
extern "C" {
26+
#endif
27+
28+
/** Manifest structure for image updates. */
29+
struct mcuboot_manifest {
30+
uint32_t format;
31+
uint32_t image_count;
32+
/* Skip a digest of the MCUBOOT_MANIFEST_IMAGE_INDEX image. */
33+
uint8_t image_hash[MCUBOOT_IMAGE_NUMBER - 1][IMAGE_HASH_SIZE];
34+
} __packed;
35+
36+
/**
37+
* @brief Check if the specified manifest has the correct format.
38+
*
39+
* @param[in] manifest The reference to the manifest structure.
40+
*
41+
* @return true on success.
42+
*/
43+
bool bootutil_verify_manifest(const struct mcuboot_manifest *manifest);
44+
45+
/**
46+
* @brief Get the image hash from the manifest.
47+
*
48+
* @param[in] manifest The reference to the manifest structure.
49+
* @param[in] image_index The index of the image to get the hash for.
50+
* Must be in range <0, MCUBOOT_IMAGE_NUMBER - 1>, but
51+
* must not be equal to MCUBOOT_MANIFEST_IMAGE_INDEX.
52+
*
53+
* @return true if hash matches with the manifest, false otherwise.
54+
*/
55+
bool bootutil_verify_manifest_image_hash(const struct mcuboot_manifest *manifest,
56+
const uint8_t *exp_hash, uint32_t image_index);
57+
58+
#ifdef __cplusplus
59+
}
60+
#endif
61+
62+
#endif /* __MCUBOOT_MANIFEST_H__ */

boot/bootutil/src/bootutil_priv.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
#include "bootutil/enc_key.h"
4545
#endif
4646

47+
#ifdef MCUBOOT_MANIFEST_UPDATES
48+
#include "bootutil/mcuboot_manifest.h"
49+
#endif /* MCUBOOT_MANIFEST_UPDATES */
50+
4751
#ifdef __cplusplus
4852
extern "C" {
4953
#endif
@@ -271,6 +275,14 @@ struct boot_loader_state {
271275
#endif
272276
} slot_usage[BOOT_IMAGE_NUMBER];
273277
#endif /* MCUBOOT_DIRECT_XIP || MCUBOOT_RAM_LOAD */
278+
279+
#if defined(MCUBOOT_MANIFEST_UPDATES)
280+
struct mcuboot_manifest manifest[BOOT_NUM_SLOTS];
281+
bool manifest_valid[BOOT_NUM_SLOTS];
282+
#if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE) || defined(MCUBOOT_SWAP_USING_OFFSET)
283+
enum boot_slot matching_manifest[BOOT_IMAGE_NUMBER][BOOT_NUM_SLOTS];
284+
#endif
285+
#endif
274286
};
275287

276288
struct boot_sector_buffer {

boot/bootutil/src/image_validate.c

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ BOOT_LOG_MODULE_DECLARE(mcuboot);
5050
#include "bootutil/mcuboot_uuid.h"
5151
#endif /* MCUBOOT_UUID_VID || MCUBOOT_UUID_CID */
5252

53+
#ifdef MCUBOOT_MANIFEST_UPDATES
54+
#include "bootutil/mcuboot_manifest.h"
55+
#endif /* MCUBOOT_MANIFEST_UPDATES */
56+
5357
#ifdef MCUBOOT_ENC_IMAGES
5458
#include "bootutil/enc_key.h"
5559
#endif
@@ -206,7 +210,7 @@ bootutil_img_validate(struct boot_loader_state *state,
206210
{
207211
#if (defined(EXPECTED_KEY_TLV) && defined(MCUBOOT_HW_KEY)) || \
208212
(defined(EXPECTED_SIG_TLV) && defined(MCUBOOT_BUILTIN_KEY)) || \
209-
defined(MCUBOOT_HW_ROLLBACK_PROT) || \
213+
defined(MCUBOOT_HW_ROLLBACK_PROT) || defined(MCUBOOT_MANIFEST_UPDATES) || \
210214
defined(MCUBOOT_UUID_VID) || defined(MCUBOOT_UUID_CID)
211215
int image_index = (state == NULL ? 0 : BOOT_CURR_IMG(state));
212216
#endif
@@ -244,6 +248,11 @@ bootutil_img_validate(struct boot_loader_state *state,
244248
uint32_t img_security_cnt = 0;
245249
FIH_DECLARE(security_counter_valid, FIH_FAILURE);
246250
#endif
251+
#ifdef MCUBOOT_MANIFEST_UPDATES
252+
bool manifest_found = false;
253+
bool manifest_valid = false;
254+
uint8_t slot = (flash_area_get_id(fap) == FLASH_AREA_IMAGE_SECONDARY(image_index) ? 1 : 0);
255+
#endif
247256
#ifdef MCUBOOT_UUID_VID
248257
struct image_uuid img_uuid_vid = {0x00};
249258
FIH_DECLARE(uuid_vid_valid, FIH_FAILURE);
@@ -357,6 +366,69 @@ bootutil_img_validate(struct boot_loader_state *state,
357366
goto out;
358367
}
359368

369+
#ifdef MCUBOOT_MANIFEST_UPDATES
370+
if (image_index == MCUBOOT_MANIFEST_IMAGE_INDEX) {
371+
if (!state->manifest_valid[slot]) {
372+
/* Manifest TLV must be processed before any of the image's hash TLV. */
373+
BOOT_LOG_ERR("bootutil_img_validate: image rejected, manifest not found before "
374+
"image %d hash", image_index);
375+
rc = -1;
376+
goto out;
377+
}
378+
/* Manifest image does not have hash in the manifest. */
379+
image_hash_valid = 1;
380+
break;
381+
}
382+
#if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE) || \
383+
defined(MCUBOOT_SWAP_USING_OFFSET)
384+
state->matching_manifest[image_index][slot] = BOOT_SLOT_NONE;
385+
/* Try to match with the primary manifest first. */
386+
if (state->manifest_valid[BOOT_SLOT_PRIMARY]) {
387+
if (bootutil_verify_manifest_image_hash(&state->manifest[BOOT_SLOT_PRIMARY], hash,
388+
image_index)) {
389+
state->matching_manifest[image_index][slot] = BOOT_SLOT_PRIMARY;
390+
}
391+
}
392+
393+
/* Try to match with the secondary manifest if not matched with the primary. */
394+
if(state->matching_manifest[image_index][slot] == BOOT_SLOT_NONE &&
395+
state->manifest_valid[BOOT_SLOT_SECONDARY]) {
396+
if (bootutil_verify_manifest_image_hash(&state->manifest[BOOT_SLOT_SECONDARY], hash,
397+
image_index)) {
398+
state->matching_manifest[image_index][slot] = BOOT_SLOT_SECONDARY;
399+
}
400+
}
401+
402+
/* No matching manifest found. */
403+
if (state->matching_manifest[image_index][slot] == BOOT_SLOT_NONE) {
404+
BOOT_LOG_ERR(
405+
"bootutil_img_validate: image rejected, no valid manifest for image %d slot %d",
406+
image_index, slot);
407+
rc = -1;
408+
goto out;
409+
} else {
410+
BOOT_LOG_INF("bootutil_img_validate: image %d slot %d matches manifest in slot %d",
411+
image_index, slot, state->matching_manifest[image_index][slot]);
412+
}
413+
#else /* MCUBOOT_SWAP_USING_SCRATCH || MCUBOOT_SWAP_USING_MOVE || MCUBOOT_SWAP_USING_OFFSET */
414+
/* Manifest image for a given slot must precede any of other images. */
415+
if (!state->manifest_valid[slot]) {
416+
/* Manifest TLV must be processed before any of the image's hash TLV. */
417+
BOOT_LOG_ERR("bootutil_img_validate: image rejected, no valid manifest for slot %d",
418+
slot);
419+
rc = -1;
420+
goto out;
421+
}
422+
423+
/* Any image, not described by the manifest is considered as invalid. */
424+
if (!bootutil_verify_manifest_image_hash(&state->manifest[slot], hash, image_index)) {
425+
BOOT_LOG_ERR(
426+
"bootutil_img_validate: image rejected, hash does not match manifest contents");
427+
FIH_SET(fih_rc, FIH_FAILURE);
428+
goto out;
429+
}
430+
#endif /* MCUBOOT_SWAP_USING_SCRATCH || MCUBOOT_SWAP_USING_MOVE || MCUBOOT_SWAP_USING_OFFSET */
431+
#endif /* MCUBOOT_MANIFEST_UPDATES */
360432
image_hash_valid = 1;
361433
break;
362434
}
@@ -485,6 +557,43 @@ bootutil_img_validate(struct boot_loader_state *state,
485557
break;
486558
}
487559
#endif /* MCUBOOT_HW_ROLLBACK_PROT */
560+
#ifdef MCUBOOT_MANIFEST_UPDATES
561+
case IMAGE_TLV_MANIFEST:
562+
{
563+
/* There can be only one manifest and must be a part of image with specific index. */
564+
if (manifest_found || image_index != MCUBOOT_MANIFEST_IMAGE_INDEX ||
565+
len != sizeof(struct mcuboot_manifest)) {
566+
BOOT_LOG_ERR(
567+
"bootutil_img_validate: image %d slot %d rejected, unexpected manifest TLV",
568+
image_index, slot);
569+
rc = -1;
570+
goto out;
571+
}
572+
573+
manifest_found = true;
574+
575+
rc = LOAD_IMAGE_DATA(hdr, fap, off, &state->manifest[slot],
576+
sizeof(struct mcuboot_manifest));
577+
if (rc) {
578+
BOOT_LOG_ERR("bootutil_img_validate: slot %d rejected, unable to load manifest",
579+
slot);
580+
goto out;
581+
}
582+
583+
manifest_valid = bootutil_verify_manifest(&state->manifest[slot]);
584+
if (!manifest_valid) {
585+
BOOT_LOG_ERR("bootutil_img_validate: slot %d rejected, invalid manifest contents",
586+
slot);
587+
rc = -1;
588+
goto out;
589+
}
590+
591+
/* The image's manifest has been successfully verified. */
592+
state->manifest_valid[slot] = true;
593+
BOOT_LOG_INF("bootutil_img_validate: slot %d manifest verified", slot);
594+
break;
595+
}
596+
#endif
488597
#ifdef MCUBOOT_UUID_VID
489598
case IMAGE_TLV_UUID_VID:
490599
{
@@ -565,6 +674,13 @@ bootutil_img_validate(struct boot_loader_state *state,
565674
}
566675
#endif
567676

677+
#ifdef MCUBOOT_MANIFEST_UPDATES
678+
if (image_index == MCUBOOT_MANIFEST_IMAGE_INDEX && (!manifest_found || !manifest_valid)) {
679+
BOOT_LOG_ERR("bootutil_img_validate: slot %d rejected, manifest missing or invalid", slot);
680+
rc = -1;
681+
goto out;
682+
}
683+
#endif
568684
#ifdef MCUBOOT_UUID_VID
569685
if (FIH_NOT_EQ(uuid_vid_valid, FIH_SUCCESS)) {
570686
rc = -1;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <mcuboot_manifest.h>
8+
9+
bool bootutil_verify_manifest(const struct mcuboot_manifest *manifest)
10+
{
11+
if (manifest == NULL) {
12+
return false;
13+
}
14+
15+
/* Currently only the simplest manifest format is supported */
16+
if (manifest->format != 0x1) {
17+
return false;
18+
}
19+
20+
if (manifest->image_count != MCUBOOT_IMAGE_NUMBER - 1) {
21+
return false;
22+
}
23+
24+
return true;
25+
}
26+
27+
bool bootutil_verify_manifest_image_hash(const struct mcuboot_manifest *manifest,
28+
const uint8_t *exp_hash, uint32_t image_index)
29+
{
30+
if (!bootutil_verify_manifest(manifest)) {
31+
return false;
32+
}
33+
34+
if (image_index >= MCUBOOT_IMAGE_NUMBER) {
35+
return false;
36+
}
37+
38+
if (image_index < MCUBOOT_MANIFEST_IMAGE_INDEX) {
39+
if (memcmp(exp_hash, manifest->image_hash[image_index], IMAGE_HASH_SIZE) == 0) {
40+
return true;
41+
}
42+
} else if (image_index > MCUBOOT_MANIFEST_IMAGE_INDEX) {
43+
if (memcmp(exp_hash, manifest->image_hash[image_index - 1], IMAGE_HASH_SIZE) == 0) {
44+
return true;
45+
}
46+
}
47+
48+
return false;
49+
}

boot/espressif/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ set(bootutil_srcs
252252
${BOOTUTIL_DIR}/src/image_ed25519.c
253253
${BOOTUTIL_DIR}/src/image_rsa.c
254254
${BOOTUTIL_DIR}/src/image_validate.c
255+
${BOOTUTIL_DIR}/src/mcuboot_manifest.c
255256
${BOOTUTIL_DIR}/src/loader.c
256257
${BOOTUTIL_DIR}/src/swap_misc.c
257258
${BOOTUTIL_DIR}/src/swap_move.c

boot/zephyr/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ zephyr_library_sources(
103103
${BOOT_DIR}/bootutil/src/bootutil_find_key.c
104104
${BOOT_DIR}/bootutil/src/bootutil_img_hash.c
105105
${BOOT_DIR}/bootutil/src/bootutil_img_security_cnt.c
106+
${BOOT_DIR}/bootutil/src/mcuboot_manifest.c
106107
${BOOT_DIR}/bootutil/src/tlv.c
107108
${BOOT_DIR}/bootutil/src/encrypted.c
108109
${BOOT_DIR}/bootutil/src/image_rsa.c

boot/zephyr/Kconfig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,32 @@ config MCUBOOT_HW_DOWNGRADE_PREVENTION_LOCK
10831083
This prevents the application from accidental updates of the counter,
10841084
that may invalidate the currently running image.
10851085

1086+
config MCUBOOT_MANIFEST_UPDATES
1087+
bool "Enable transactional updates"
1088+
select EXPERIMENTAL
1089+
depends on (UPDATEABLE_IMAGE_NUMBER > 1) && BOOT_DIRECT_XIP
1090+
help
1091+
If y, enables support for transactional updates using manifests.
1092+
This allows multiple images to be updated atomically. The manifest
1093+
is a separate TLV which contains a list of images to update and
1094+
their expected hash values. The manifest TLV is a part of an image
1095+
that is signed to prevent tampering.
1096+
The manifest must be transferred as part of the image with index 0.
1097+
It can be a dedicated image, or part of an existing image.
1098+
If the second option is selected, all updates must contain an update
1099+
for image 0.
1100+
1101+
if MCUBOOT_MANIFEST_UPDATES
1102+
1103+
config MCUBOOT_MANIFEST_IMAGE_INDEX
1104+
int "Index of the image that must include manifest"
1105+
default 0
1106+
range 0 UPDATEABLE_IMAGE_NUMBER
1107+
help
1108+
Specifies the index of the image that must include the manifest.
1109+
1110+
endif # MCUBOOT_MANIFEST_UPDATES
1111+
10861112
config MCUBOOT_UUID_VID
10871113
bool "Expect vendor unique identifier in image's TLV"
10881114
help

boot/zephyr/include/mcuboot_config/mcuboot_config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,14 @@
237237
#define MCUBOOT_HW_ROLLBACK_PROT_LOCK
238238
#endif
239239

240+
#ifdef CONFIG_MCUBOOT_MANIFEST_UPDATES
241+
#define MCUBOOT_MANIFEST_UPDATES
242+
243+
#ifdef CONFIG_MCUBOOT_MANIFEST_IMAGE_INDEX
244+
#define MCUBOOT_MANIFEST_IMAGE_INDEX CONFIG_MCUBOOT_MANIFEST_IMAGE_INDEX
245+
#endif /* CONFIG_MCUBOOT_MANIFEST_IMAGE_INDEX */
246+
#endif /* CONFIG_MCUBOOT_MANIFEST_UPDATES */
247+
240248
#ifdef CONFIG_MCUBOOT_UUID_VID
241249
#define MCUBOOT_UUID_VID
242250
#endif

0 commit comments

Comments
 (0)