Skip to content

Commit 98b6d9a

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 e19f019 commit 98b6d9a

File tree

7 files changed

+231
-3
lines changed

7 files changed

+231
-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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
#include "bootutil/enc_key.h"
4444
#endif
4545

46+
#ifdef MCUBOOT_MANIFEST_UPDATES
47+
#include "bootutil/mcuboot_manifest.h"
48+
#endif /* MCUBOOT_MANIFEST_UPDATES */
49+
4650
#ifdef __cplusplus
4751
extern "C" {
4852
#endif
@@ -287,6 +291,11 @@ struct boot_loader_state {
287291
#endif
288292
} slot_usage[BOOT_IMAGE_NUMBER];
289293
#endif /* MCUBOOT_DIRECT_XIP || MCUBOOT_RAM_LOAD */
294+
295+
#if defined(MCUBOOT_MANIFEST_UPDATES)
296+
struct mcuboot_manifest manifest[BOOT_NUM_SLOTS];
297+
bool manifest_valid[BOOT_NUM_SLOTS];
298+
#endif
290299
};
291300

292301
struct boot_sector_buffer {

boot/bootutil/src/image_validate.c

Lines changed: 86 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
#if defined(MCUBOOT_DECOMPRESS_IMAGES)
5458
#include <nrf_compress/implementation.h>
5559
#include <compression/decompression.h>
@@ -212,7 +216,7 @@ bootutil_img_validate(struct boot_loader_state *state,
212216
{
213217
#if (defined(EXPECTED_KEY_TLV) && defined(MCUBOOT_HW_KEY)) || defined(MCUBOOT_HW_ROLLBACK_PROT) \
214218
|| defined(MCUBOOT_UUID_VID) || defined(MCUBOOT_UUID_CID) || defined(MCUBOOT_DECOMPRESS_IMAGES) \
215-
|| defined(MCUBOOT_BUILTIN_KEY)
219+
|| defined(MCUBOOT_BUILTIN_KEY) || defined(MCUBOOT_MANIFEST_UPDATES)
216220
int image_index = (state == NULL ? 0 : BOOT_CURR_IMG(state));
217221
#endif
218222
uint32_t off;
@@ -258,6 +262,12 @@ bootutil_img_validate(struct boot_loader_state *state,
258262
goto out;
259263
}
260264
#endif
265+
#ifdef MCUBOOT_MANIFEST_UPDATES
266+
bool manifest_found = false;
267+
bool manifest_valid = false;
268+
uint8_t slot = (flash_area_get_id(fap) == FLASH_AREA_IMAGE_SECONDARY(image_index) ? 1 : 0);
269+
const uint8_t *image_hash = NULL;
270+
#endif
261271
#ifdef MCUBOOT_UUID_VID
262272
struct image_uuid img_uuid_vid = {0x00};
263273
FIH_DECLARE(uuid_vid_valid, FIH_FAILURE);
@@ -431,6 +441,41 @@ bootutil_img_validate(struct boot_loader_state *state,
431441
goto out;
432442
}
433443

444+
#ifdef MCUBOOT_MANIFEST_UPDATES
445+
/* If manifest is present, verify that the image hash matches the
446+
* one in the manifest.
447+
*/
448+
if (!state->manifest_valid[slot]) {
449+
/* Manifest TLV must be processed before any of the image's hash TLV. */
450+
BOOT_LOG_INF("bootutil_img_validate: image rejected, no valid manifest for slot %d",
451+
slot);
452+
rc = -1;
453+
goto out;
454+
}
455+
456+
if (image_index == MCUBOOT_MANIFEST_IMAGE_NUMBER) {
457+
/* Manifest image does not have hash in the manifest. */
458+
image_hash_valid = 1;
459+
break;
460+
}
461+
462+
/* Any image, not described by the manifest is considered as invalid. */
463+
image_hash = bootutil_get_image_hash(&state->manifest[slot], image_index);
464+
if (image_hash == NULL) {
465+
/* Manifest TLV must be processed before any of the image's hash TLV. */
466+
BOOT_LOG_INF("bootutil_img_validate: image rejected, no valid manifest for image %d slot %d",
467+
image_index, slot);
468+
rc = -1;
469+
goto out;
470+
}
471+
472+
FIH_CALL(boot_fih_memequal, fih_rc, hash, image_hash, sizeof(hash));
473+
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
474+
BOOT_LOG_INF("bootutil_img_validate: image rejected, hash does not match manifest contents");
475+
FIH_SET(fih_rc, FIH_FAILURE);
476+
goto out;
477+
}
478+
#endif
434479
image_hash_valid = 1;
435480
break;
436481
}
@@ -568,6 +613,39 @@ bootutil_img_validate(struct boot_loader_state *state,
568613
break;
569614
}
570615
#endif /* MCUBOOT_HW_ROLLBACK_PROT */
616+
#ifdef MCUBOOT_MANIFEST_UPDATES
617+
case IMAGE_TLV_MANIFEST:
618+
{
619+
/* There can be only one manifest and must be a part of image with specific index. */
620+
if (manifest_found || image_index != MCUBOOT_MANIFEST_IMAGE_NUMBER ||
621+
len != sizeof(struct mcuboot_manifest) || state->manifest_valid[slot]) {
622+
BOOT_LOG_INF("bootutil_img_validate: image %d slot %d rejected, unexpected manifest TLV",
623+
image_index, slot);
624+
rc = -1;
625+
goto out;
626+
}
627+
628+
manifest_found = true;
629+
630+
rc = LOAD_IMAGE_DATA(hdr, fap, off, &state->manifest[slot], sizeof(struct mcuboot_manifest));
631+
if (rc) {
632+
BOOT_LOG_INF("bootutil_img_validate: slot %d rejected, unable to load manifest", slot);
633+
goto out;
634+
}
635+
636+
manifest_valid = bootutil_verify_manifest(&state->manifest[slot]);
637+
if (!manifest_valid) {
638+
BOOT_LOG_INF("bootutil_img_validate: slot %d rejected, invalid manifest contents", slot);
639+
rc = -1;
640+
goto out;
641+
}
642+
643+
/* The image's manifest has been successfully verified. */
644+
state->manifest_valid[slot] = true;
645+
BOOT_LOG_INF("bootutil_img_validate: slot %d manifest verified", slot);
646+
break;
647+
}
648+
#endif
571649
#ifdef MCUBOOT_UUID_VID
572650
case IMAGE_TLV_UUID_VID:
573651
{
@@ -654,6 +732,13 @@ bootutil_img_validate(struct boot_loader_state *state,
654732
skip_security_counter_check:
655733
#endif
656734

735+
#ifdef MCUBOOT_MANIFEST_UPDATES
736+
if (image_index == MCUBOOT_MANIFEST_IMAGE_NUMBER && (!manifest_found || !manifest_valid)) {
737+
BOOT_LOG_INF("bootutil_img_validate: slot %d rejected, manifest missing or invalid", slot);
738+
rc = -1;
739+
goto out;
740+
}
741+
#endif
657742
#ifdef MCUBOOT_UUID_VID
658743
if (FIH_NOT_EQ(uuid_vid_valid, FIH_SUCCESS)) {
659744
rc = -1;

boot/zephyr/Kconfig

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

11601160
endchoice
11611161

1162+
config MCUBOOT_MANIFEST_UPDATES
1163+
bool "Enable transactional updates"
1164+
imply LOAD_AND_VALIDATE_IMAGES_HOOKS
1165+
help
1166+
If y, enables support for transactional updates using manifests.
1167+
This allows multiple images to be updated atomically. The manifest
1168+
is a separate TLV which contains a list of images to update and
1169+
their expected hash values. The manifest TLV is a part of an image
1170+
that is signed to prevent tampering.
1171+
The manifest must be transferred as part of the image with index 0.
1172+
It can be a dedicated image, or part of an existing image.
1173+
If the second option is selected, all updates must contain an update
1174+
for image 0.
1175+
1176+
if MCUBOOT_MANIFEST_UPDATES
1177+
1178+
config MCUBOOT_MANIFEST_IMAGE_NUMBER
1179+
int "Number of image that must include manifest"
1180+
default 0
1181+
range 0 UPDATEABLE_IMAGE_NUMBER
1182+
help
1183+
Specifies the index of the image that must include the manifest.
1184+
If MCUBOOT_MANIFEST_USE_DEDICATED_IMAGE is selected, this index
1185+
specifies the index of the dedicated image that contains only
1186+
the manifest.
1187+
1188+
endif # MCUBOOT_MANIFEST_UPDATES
1189+
11621190
config MCUBOOT_UUID_VID
11631191
bool "Expect vendor unique identifier in image's TLV"
11641192
help

boot/zephyr/include/mcuboot_config/mcuboot_config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,14 @@
243243
#define MCUBOOT_HW_ROLLBACK_PROT_COUNTER_LIMITED
244244
#endif
245245

246+
#ifdef CONFIG_MCUBOOT_MANIFEST_UPDATES
247+
#define MCUBOOT_MANIFEST_UPDATES
248+
249+
#ifdef CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER
250+
#define MCUBOOT_MANIFEST_IMAGE_NUMBER CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER
251+
#endif /* CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER */
252+
#endif /* CONFIG_MCUBOOT_MANIFEST_UPDATES */
253+
246254
#ifdef CONFIG_MCUBOOT_UUID_VID
247255
#define MCUBOOT_UUID_VID
248256
#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)