Skip to content

Commit af4fc45

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 56538cf commit af4fc45

File tree

7 files changed

+275
-3
lines changed

7 files changed

+275
-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: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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[BOOT_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 != BOOT_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, BOOT_IMAGE_NUMBER - 1>, but
67+
* must not be equal to MCUBOOT_MANIFEST_IMAGE_NUMBER.
68+
*
69+
* @return A pointer to the image hash, or NULL if the image_index is out of range
70+
* of allowed values.
71+
*/
72+
static inline const uint8_t *bootutil_get_image_hash(const struct mcuboot_manifest *manifest,
73+
uint32_t image_index)
74+
{
75+
if (!bootutil_verify_manifest(manifest)) {
76+
return NULL;
77+
}
78+
79+
if (image_index >= BOOT_IMAGE_NUMBER) {
80+
return NULL;
81+
}
82+
83+
if (image_index < MCUBOOT_MANIFEST_IMAGE_NUMBER) {
84+
return manifest->image_hash[image_index];
85+
} else if (image_index > MCUBOOT_MANIFEST_IMAGE_NUMBER) {
86+
return manifest->image_hash[image_index - 1];
87+
}
88+
89+
return NULL;
90+
}
91+
92+
#ifdef __cplusplus
93+
}
94+
#endif
95+
96+
#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: 127 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,12 @@ 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+
const uint8_t *image_hash = NULL;
256+
#endif
247257
#ifdef MCUBOOT_UUID_VID
248258
struct image_uuid img_uuid_vid = {0x00};
249259
FIH_DECLARE(uuid_vid_valid, FIH_FAILURE);
@@ -356,6 +366,82 @@ bootutil_img_validate(struct boot_loader_state *state,
356366
goto out;
357367
}
358368

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_INF("bootutil_img_validate: image rejected, manifest not found before image %d hash",
374+
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) || defined(MCUBOOT_SWAP_USING_OFFSET)
383+
state->matching_manifest[image_index][slot] = BOOT_SLOT_NONE;
384+
/* Try to match with the primary manifest first. */
385+
if (state->manifest_valid[BOOT_SLOT_PRIMARY]) {
386+
image_hash = bootutil_get_image_hash(&state->manifest[BOOT_SLOT_PRIMARY], image_index);
387+
if (image_hash != NULL) {
388+
FIH_CALL(boot_fih_memequal, fih_rc, hash, image_hash, sizeof(hash));
389+
if (FIH_EQ(fih_rc, FIH_SUCCESS)) {
390+
state->matching_manifest[image_index][slot] = BOOT_SLOT_PRIMARY;
391+
}
392+
}
393+
}
394+
395+
/* Try to match with the secondary manifest if not matched with the primary. */
396+
if(state->matching_manifest[image_index][slot] == BOOT_SLOT_NONE &&
397+
state->manifest_valid[BOOT_SLOT_SECONDARY]) {
398+
image_hash = bootutil_get_image_hash(&state->manifest[BOOT_SLOT_SECONDARY], image_index);
399+
if (image_hash != NULL) {
400+
FIH_CALL(boot_fih_memequal, fih_rc, hash, image_hash, sizeof(hash));
401+
if (FIH_EQ(fih_rc, FIH_SUCCESS)) {
402+
state->matching_manifest[image_index][slot] = BOOT_SLOT_SECONDARY;
403+
}
404+
}
405+
}
406+
407+
/* No matching manifest found. */
408+
if (state->matching_manifest[image_index][slot] == BOOT_SLOT_NONE) {
409+
BOOT_LOG_INF("bootutil_img_validate: image rejected, no valid manifest for image %d slot %d",
410+
image_index, slot);
411+
rc = -1;
412+
goto out;
413+
} else {
414+
BOOT_LOG_INF("bootutil_img_validate: image %d slot %d matches manifest in slot %d",
415+
image_index, slot, state->matching_manifest[image_index][slot]);
416+
}
417+
#else /* MCUBOOT_SWAP_USING_SCRATCH || MCUBOOT_SWAP_USING_MOVE || MCUBOOT_SWAP_USING_OFFSET */
418+
/* Manifest image for a given slot must precede any of other images. */
419+
if (!state->manifest_valid[slot]) {
420+
/* Manifest TLV must be processed before any of the image's hash TLV. */
421+
BOOT_LOG_INF("bootutil_img_validate: image rejected, no valid manifest for slot %d",
422+
slot);
423+
rc = -1;
424+
goto out;
425+
}
426+
427+
/* Any image, not described by the manifest is considered as invalid. */
428+
image_hash = bootutil_get_image_hash(&state->manifest[slot], image_index);
429+
if (image_hash == NULL) {
430+
/* Manifest TLV must be processed before any of the image's hash TLV. */
431+
BOOT_LOG_INF("bootutil_img_validate: image rejected, no valid manifest for image %d slot %d",
432+
image_index, slot);
433+
rc = -1;
434+
goto out;
435+
}
436+
437+
FIH_CALL(boot_fih_memequal, fih_rc, hash, image_hash, sizeof(hash));
438+
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
439+
BOOT_LOG_INF("bootutil_img_validate: image rejected, hash does not match manifest contents");
440+
FIH_SET(fih_rc, FIH_FAILURE);
441+
goto out;
442+
}
443+
#endif /* MCUBOOT_SWAP_USING_SCRATCH || MCUBOOT_SWAP_USING_MOVE || MCUBOOT_SWAP_USING_OFFSET */
444+
#endif /* MCUBOOT_MANIFEST_UPDATES */
359445
image_hash_valid = 1;
360446
break;
361447
}
@@ -484,6 +570,39 @@ bootutil_img_validate(struct boot_loader_state *state,
484570
break;
485571
}
486572
#endif /* MCUBOOT_HW_ROLLBACK_PROT */
573+
#ifdef MCUBOOT_MANIFEST_UPDATES
574+
case IMAGE_TLV_MANIFEST:
575+
{
576+
/* There can be only one manifest and must be a part of image with specific index. */
577+
if (manifest_found || image_index != MCUBOOT_MANIFEST_IMAGE_NUMBER ||
578+
len != sizeof(struct mcuboot_manifest)) {
579+
BOOT_LOG_INF("bootutil_img_validate: image %d slot %d rejected, unexpected manifest TLV",
580+
image_index, slot);
581+
rc = -1;
582+
goto out;
583+
}
584+
585+
manifest_found = true;
586+
587+
rc = LOAD_IMAGE_DATA(hdr, fap, off, &state->manifest[slot], sizeof(struct mcuboot_manifest));
588+
if (rc) {
589+
BOOT_LOG_INF("bootutil_img_validate: slot %d rejected, unable to load manifest", slot);
590+
goto out;
591+
}
592+
593+
manifest_valid = bootutil_verify_manifest(&state->manifest[slot]);
594+
if (!manifest_valid) {
595+
BOOT_LOG_INF("bootutil_img_validate: slot %d rejected, invalid manifest contents", slot);
596+
rc = -1;
597+
goto out;
598+
}
599+
600+
/* The image's manifest has been successfully verified. */
601+
state->manifest_valid[slot] = true;
602+
BOOT_LOG_INF("bootutil_img_validate: slot %d manifest verified", slot);
603+
break;
604+
}
605+
#endif
487606
#ifdef MCUBOOT_UUID_VID
488607
case IMAGE_TLV_UUID_VID:
489608
{
@@ -564,6 +683,13 @@ bootutil_img_validate(struct boot_loader_state *state,
564683
}
565684
#endif
566685

686+
#ifdef MCUBOOT_MANIFEST_UPDATES
687+
if (image_index == MCUBOOT_MANIFEST_IMAGE_NUMBER && (!manifest_found || !manifest_valid)) {
688+
BOOT_LOG_INF("bootutil_img_validate: slot %d rejected, manifest missing or invalid", slot);
689+
rc = -1;
690+
goto out;
691+
}
692+
#endif
567693
#ifdef MCUBOOT_UUID_VID
568694
if (FIH_NOT_EQ(uuid_vid_valid, FIH_SUCCESS)) {
569695
rc = -1;

boot/zephyr/Kconfig

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,34 @@ config MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_LIMITED
10621062

10631063
endchoice
10641064

1065+
config MCUBOOT_MANIFEST_UPDATES
1066+
bool "Enable transactional updates"
1067+
select EXPERIMENTAL
1068+
help
1069+
If y, enables support for transactional updates using manifests.
1070+
This allows multiple images to be updated atomically. The manifest
1071+
is a separate TLV which contains a list of images to update and
1072+
their expected hash values. The manifest TLV is a part of an image
1073+
that is signed to prevent tampering.
1074+
The manifest must be transferred as part of the image with index 0.
1075+
It can be a dedicated image, or part of an existing image.
1076+
If the second option is selected, all updates must contain an update
1077+
for image 0.
1078+
1079+
if MCUBOOT_MANIFEST_UPDATES
1080+
1081+
config MCUBOOT_MANIFEST_IMAGE_NUMBER
1082+
int "Number of image that must include manifest"
1083+
default 0
1084+
range 0 UPDATEABLE_IMAGE_NUMBER
1085+
help
1086+
Specifies the index of the image that must include the manifest.
1087+
If MCUBOOT_MANIFEST_USE_DEDICATED_IMAGE is selected, this index
1088+
specifies the index of the dedicated image that contains only
1089+
the manifest.
1090+
1091+
endif # MCUBOOT_MANIFEST_UPDATES
1092+
10651093
config MCUBOOT_UUID_VID
10661094
bool "Expect vendor unique identifier in image's TLV"
10671095
help

boot/zephyr/include/mcuboot_config/mcuboot_config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,14 @@
233233
#define MCUBOOT_HW_ROLLBACK_PROT_COUNTER_LIMITED
234234
#endif
235235

236+
#ifdef CONFIG_MCUBOOT_MANIFEST_UPDATES
237+
#define MCUBOOT_MANIFEST_UPDATES
238+
239+
#ifdef CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER
240+
#define MCUBOOT_MANIFEST_IMAGE_NUMBER CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER
241+
#endif /* CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER */
242+
#endif /* CONFIG_MCUBOOT_MANIFEST_UPDATES */
243+
236244
#ifdef CONFIG_MCUBOOT_UUID_VID
237245
#define MCUBOOT_UUID_VID
238246
#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)