Skip to content

Commit 9951400

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 d14ba22 commit 9951400

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);
@@ -356,6 +365,69 @@ bootutil_img_validate(struct boot_loader_state *state,
356365
goto out;
357366
}
358367

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

676+
#ifdef MCUBOOT_MANIFEST_UPDATES
677+
if (image_index == MCUBOOT_MANIFEST_IMAGE_NUMBER && (!manifest_found || !manifest_valid)) {
678+
BOOT_LOG_ERR("bootutil_img_validate: slot %d rejected, manifest missing or invalid", slot);
679+
rc = -1;
680+
goto out;
681+
}
682+
#endif
567683
#ifdef MCUBOOT_UUID_VID
568684
if (FIH_NOT_EQ(uuid_vid_valid, FIH_SUCCESS)) {
569685
rc = -1;

boot/zephyr/Kconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,31 @@ config MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_LIMITED
10721072

10731073
endchoice
10741074

1075+
config MCUBOOT_MANIFEST_UPDATES
1076+
bool "Enable transactional updates"
1077+
select EXPERIMENTAL
1078+
help
1079+
If y, enables support for transactional updates using manifests.
1080+
This allows multiple images to be updated atomically. The manifest
1081+
is a separate TLV which contains a list of images to update and
1082+
their expected hash values. The manifest TLV is a part of an image
1083+
that is signed to prevent tampering.
1084+
The manifest must be transferred as part of the image with index 0.
1085+
It can be a dedicated image, or part of an existing image.
1086+
If the second option is selected, all updates must contain an update
1087+
for image 0.
1088+
1089+
if MCUBOOT_MANIFEST_UPDATES
1090+
1091+
config MCUBOOT_MANIFEST_IMAGE_NUMBER
1092+
int "Number of image that must include manifest"
1093+
default 0
1094+
range 0 UPDATEABLE_IMAGE_NUMBER
1095+
help
1096+
Specifies the index of the image that must include the manifest.
1097+
1098+
endif # MCUBOOT_MANIFEST_UPDATES
1099+
10751100
config MCUBOOT_UUID_VID
10761101
bool "Expect vendor unique identifier in image's TLV"
10771102
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)