Skip to content

Commit cf83a56

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 <tomasz.chyrowicz@nordicsemi.no>
1 parent 900e29d commit cf83a56

File tree

7 files changed

+265
-3
lines changed

7 files changed

+265
-3
lines changed

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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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_NUMBER 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+
static inline bool bootutil_verify_manifest(const struct mcuboot_manifest *manifest)
44+
{
45+
if (manifest == NULL) {
46+
return false;
47+
}
48+
49+
/* Currently only the simplest manifest format is supported */
50+
if (manifest->format != 0x1) {
51+
return false;
52+
}
53+
54+
if (manifest->image_count != MCUBOOT_IMAGE_NUMBER - 1) {
55+
return false;
56+
}
57+
58+
return true;
59+
}
60+
61+
/**
62+
* @brief Get the image hash from the manifest.
63+
*
64+
* @param[in] manifest The reference to the manifest structure.
65+
* @param[in] image_index The index of the image to get the hash for.
66+
* Must be in range <0, MCUBOOT_IMAGE_NUMBER - 1>, but
67+
* must not be equal to MCUBOOT_MANIFEST_IMAGE_NUMBER.
68+
*
69+
* @return true if hash matches with the manifest, false otherwise.
70+
*/
71+
static inline bool bootutil_verify_manifest_image_hash(const struct mcuboot_manifest *manifest,
72+
const uint8_t *exp_hash, uint32_t image_index)
73+
{
74+
if (!bootutil_verify_manifest(manifest)) {
75+
return false;
76+
}
77+
78+
if (image_index >= MCUBOOT_IMAGE_NUMBER) {
79+
return false;
80+
}
81+
82+
if (image_index < MCUBOOT_MANIFEST_IMAGE_NUMBER) {
83+
if (memcmp(exp_hash, manifest->image_hash[image_index], IMAGE_HASH_SIZE) == 0) {
84+
return true;
85+
}
86+
} else if (image_index > MCUBOOT_MANIFEST_IMAGE_NUMBER) {
87+
if (memcmp(exp_hash, manifest->image_hash[image_index - 1], IMAGE_HASH_SIZE) == 0) {
88+
return true;
89+
}
90+
}
91+
92+
return false;
93+
}
94+
95+
#ifdef __cplusplus
96+
}
97+
#endif
98+
99+
#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_NUMBER) {
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_NUMBER ||
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_NUMBER && (!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;

boot/zephyr/Kconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,31 @@ config MCUBOOT_HW_DOWNGRADE_PREVENTION_LOCK
10831083

10841084
endchoice
10851085

1086+
config MCUBOOT_MANIFEST_UPDATES
1087+
bool "Enable transactional updates"
1088+
select EXPERIMENTAL
1089+
help
1090+
If y, enables support for transactional updates using manifests.
1091+
This allows multiple images to be updated atomically. The manifest
1092+
is a separate TLV which contains a list of images to update and
1093+
their expected hash values. The manifest TLV is a part of an image
1094+
that is signed to prevent tampering.
1095+
The manifest must be transferred as part of the image with index 0.
1096+
It can be a dedicated image, or part of an existing image.
1097+
If the second option is selected, all updates must contain an update
1098+
for image 0.
1099+
1100+
if MCUBOOT_MANIFEST_UPDATES
1101+
1102+
config MCUBOOT_MANIFEST_IMAGE_NUMBER
1103+
int "Number of image that must include manifest"
1104+
default 0
1105+
range 0 UPDATEABLE_IMAGE_NUMBER
1106+
help
1107+
Specifies the index of the image that must include the manifest.
1108+
1109+
endif # MCUBOOT_MANIFEST_UPDATES
1110+
10861111
config MCUBOOT_UUID_VID
10871112
bool "Expect vendor unique identifier in image's TLV"
10881113
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_NUMBER
244+
#define MCUBOOT_MANIFEST_IMAGE_NUMBER CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER
245+
#endif /* CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER */
246+
#endif /* CONFIG_MCUBOOT_MANIFEST_UPDATES */
247+
240248
#ifdef CONFIG_MCUBOOT_UUID_VID
241249
#define MCUBOOT_UUID_VID
242250
#endif

docs/design.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ struct image_tlv {
150150
* ...
151151
* 0xffa0 - 0xfffe
152152
*/
153-
#define IMAGE_TLV_UUID_VID 0x80 /* Vendor unique identifier */
154-
#define IMAGE_TLV_UUID_CID 0x81 /* Device class unique identifier */
153+
#define IMAGE_TLV_UUID_VID 0x74 /* Vendor unique identifier */
154+
#define IMAGE_TLV_UUID_CID 0x75 /* Device class unique identifier */
155+
#define IMAGE_TLV_MANIFEST 0x76 /* Transaction manifest */
155156
```
156157
157158
Optional type-length-value records (TLVs) containing image metadata are placed

0 commit comments

Comments
 (0)