Skip to content

Commit 4afdd94

Browse files
committed
mcuboot: Add hook for manifest-based boot
Add a bootloader hook that implements manifest-based boot logic. Signed-off-by: Tomasz Chyrowicz <[email protected]>
1 parent 43552f8 commit 4afdd94

File tree

4 files changed

+350
-4
lines changed

4 files changed

+350
-4
lines changed

modules/mcuboot/boot/zephyr/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,11 @@ config FIND_NEXT_SLOT_HOOK_BOOT_REQ
172172
help
173173
This will read the image preference from the bootloader requests
174174
module and if found, alter the Direct XIP slot selection.
175+
176+
config NRF_MCUBOOT_LOAD_AND_VALIDATE_MANIFEST_IMAGES
177+
bool "Enable load_and_validate_images hook that uses manifest to boot images"
178+
default y if MCUBOOT_MANIFEST_UPDATES
179+
depends on LOAD_AND_VALIDATE_IMAGES_HOOKS
180+
help
181+
This will enable a hook that validates images based on the manifest
182+
information. This is required when using manifest updates.

modules/mcuboot/hooks/CMakeLists.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ if(CONFIG_BOOT_IMAGE_ACCESS_HOOKS)
1212
endif()
1313
endif()
1414

15-
if(CONFIG_FIND_NEXT_SLOT_HOOKS)
15+
if(CONFIG_FIND_NEXT_SLOT_HOOK_BOOT_REQ OR CONFIG_NRF_MCUBOOT_LOAD_AND_VALIDATE_MANIFEST_IMAGES)
16+
zephyr_library()
17+
zephyr_library_link_libraries(MCUBOOT_BOOTUTIL)
18+
1619
if(CONFIG_FIND_NEXT_SLOT_HOOK_BOOT_REQ)
17-
zephyr_library()
1820
zephyr_library_sources(hooks_find_next_slot.c)
19-
zephyr_library_link_libraries(MCUBOOT_BOOTUTIL)
21+
endif()
22+
if(CONFIG_NRF_MCUBOOT_LOAD_AND_VALIDATE_MANIFEST_IMAGES)
23+
zephyr_library_sources(hooks_load_and_validate_images.c)
2024
endif()
2125
endif()
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright (c) 2016-2020 Linaro LTD
5+
* Copyright (c) 2016-2019 JUUL Labs
6+
* Copyright (c) 2019-2023 Arm Limited
7+
* Copyright (c) 2024-2025 Nordic Semiconductor ASA
8+
*
9+
* Original license:
10+
*
11+
* Licensed to the Apache Software Foundation (ASF) under one
12+
* or more contributor license agreements. See the NOTICE file
13+
* distributed with this work for additional information
14+
* regarding copyright ownership. The ASF licenses this file
15+
* to you under the Apache License, Version 2.0 (the
16+
* "License"); you may not use this file except in compliance
17+
* with the License. You may obtain a copy of the License at
18+
*
19+
* http://www.apache.org/licenses/LICENSE-2.0
20+
*
21+
* Unless required by applicable law or agreed to in writing,
22+
* software distributed under the License is distributed on an
23+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
24+
* KIND, either express or implied. See the License for the
25+
* specific language governing permissions and limitations
26+
* under the License.
27+
*/
28+
29+
/**
30+
* This file provides an interface to the manifest-based boot loader.
31+
* Functions defined in this file should only be called while the boot loader is
32+
* running.
33+
*/
34+
35+
#include <stdbool.h>
36+
#include <../../bootutil/src/bootutil_priv.h>
37+
#include "bootutil/bootutil_log.h"
38+
#include "bootutil/fault_injection_hardening.h"
39+
#include "bootutil/ramload.h"
40+
#include "bootutil/boot_hooks.h"
41+
#include "bootutil/mcuboot_manifest.h"
42+
43+
BOOT_LOG_MODULE_DECLARE(mcuboot);
44+
45+
#if defined(MCUBOOT_DIRECT_XIP)
46+
/**
47+
* Check if image in slot has been set with specific ROM address to run from
48+
* and whether the slot starts at that address.
49+
*
50+
* @returns 0 if IMAGE_F_ROM_FIXED flag is not set;
51+
* 0 if IMAGE_F_ROM_FIXED flag is set and ROM address specified in
52+
* header matches the slot address;
53+
* 1 if IMF_F_ROM_FIXED flag is set but ROM address specified in header
54+
* does not match the slot address.
55+
*/
56+
static bool boot_rom_address_check(struct boot_loader_state *state)
57+
{
58+
uint32_t active_slot;
59+
const struct image_header *hdr;
60+
uint32_t f_off;
61+
62+
active_slot = state->slot_usage[BOOT_CURR_IMG(state)].active_slot;
63+
hdr = boot_img_hdr(state, active_slot);
64+
f_off = boot_img_slot_off(state, active_slot);
65+
66+
if (hdr->ih_flags & IMAGE_F_ROM_FIXED && hdr->ih_load_addr != f_off) {
67+
BOOT_LOG_WRN("Image in %s slot at 0x%x has been built for offset 0x%x"
68+
", skipping",
69+
active_slot == 0 ? "primary" : "secondary", f_off, hdr->ih_load_addr);
70+
71+
/* The image is not bootable from this slot. */
72+
return 1;
73+
}
74+
75+
return 0;
76+
}
77+
#endif
78+
79+
/**
80+
* Finds the slot containing the image with the highest version number for the
81+
* current image.
82+
*
83+
* @param state Boot loader status information.
84+
*
85+
* @return BOOT_SLOT_NONE if no available slot found, number of
86+
* the found slot otherwise.
87+
*/
88+
static uint32_t find_slot_with_highest_version(struct boot_loader_state *state)
89+
{
90+
uint32_t slot;
91+
uint32_t candidate_slot = BOOT_SLOT_NONE;
92+
int rc;
93+
94+
for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
95+
if (state->slot_usage[BOOT_CURR_IMG(state)].slot_available[slot]) {
96+
if (candidate_slot == BOOT_SLOT_NONE) {
97+
candidate_slot = slot;
98+
} else {
99+
rc = boot_version_cmp(&boot_img_hdr(state, slot)->ih_ver,
100+
&boot_img_hdr(state, candidate_slot)->ih_ver);
101+
if (rc == 1) {
102+
/* The version of the image being examined is greater than
103+
* the version of the current candidate.
104+
*/
105+
candidate_slot = slot;
106+
}
107+
}
108+
}
109+
}
110+
111+
return candidate_slot;
112+
}
113+
114+
#if (defined(MCUBOOT_DIRECT_XIP) && defined(MCUBOOT_DIRECT_XIP_REVERT)) || \
115+
(defined(MCUBOOT_RAM_LOAD) && defined(MCUBOOT_RAM_LOAD_REVERT))
116+
/**
117+
* Checks whether the active slot of the current image was previously selected
118+
* to run. Erases the image if it was selected but its execution failed,
119+
* otherwise marks it as selected if it has not been before.
120+
*
121+
* @param state Boot loader status information.
122+
*
123+
* @return 0 on success; nonzero on failure.
124+
*/
125+
static int boot_select_or_erase(struct boot_loader_state *state)
126+
{
127+
const struct flash_area *fap = NULL;
128+
int rc;
129+
uint32_t active_slot;
130+
struct boot_swap_state *active_swap_state;
131+
132+
active_slot = state->slot_usage[BOOT_CURR_IMG(state)].active_slot;
133+
134+
fap = BOOT_IMG_AREA(state, active_slot);
135+
assert(fap != NULL);
136+
137+
active_swap_state = &(state->slot_usage[BOOT_CURR_IMG(state)].swap_state);
138+
139+
memset(active_swap_state, 0, sizeof(struct boot_swap_state));
140+
rc = boot_read_swap_state(fap, active_swap_state);
141+
assert(rc == 0);
142+
143+
if (active_swap_state->magic != BOOT_MAGIC_GOOD ||
144+
(active_swap_state->copy_done == BOOT_FLAG_SET &&
145+
active_swap_state->image_ok != BOOT_FLAG_SET)) {
146+
/*
147+
* A reboot happened without the image being confirmed at
148+
* runtime or its trailer is corrupted/invalid. Erase the image
149+
* to prevent it from being selected again on the next reboot.
150+
*/
151+
BOOT_LOG_DBG("Erasing faulty image in the %s slot.",
152+
(active_slot == BOOT_SLOT_PRIMARY) ? "primary" : "secondary");
153+
rc = boot_scramble_slot(fap, active_slot);
154+
assert(rc == 0);
155+
rc = -1;
156+
} else {
157+
if (active_swap_state->copy_done != BOOT_FLAG_SET) {
158+
if (active_swap_state->copy_done == BOOT_FLAG_BAD) {
159+
BOOT_LOG_DBG("The copy_done flag had an unexpected value. Its "
160+
"value was neither 'set' nor 'unset', but 'bad'.");
161+
}
162+
/*
163+
* Set the copy_done flag, indicating that the image has been
164+
* selected to boot. It can be set in advance, before even
165+
* validating the image, because in case the validation fails, the
166+
* entire image slot will be erased (including the trailer).
167+
*/
168+
rc = boot_write_copy_done(fap);
169+
if (rc != 0) {
170+
BOOT_LOG_WRN("Failed to set copy_done flag of the image in "
171+
"the %s slot.",
172+
(active_slot == BOOT_SLOT_PRIMARY) ? "primary"
173+
: "secondary");
174+
rc = 0;
175+
}
176+
}
177+
}
178+
179+
return rc;
180+
}
181+
#endif /* MCUBOOT_DIRECT_XIP && MCUBOOT_DIRECT_XIP_REVERT || \
182+
MCUBOOT_RAM_LOAD && MCUBOOT_RAM_LOAD_REVERT */
183+
184+
/**
185+
* Tries to load and validate a single slot.
186+
*
187+
* @param state Boot loader status information.
188+
*
189+
* @return 0 on success; nonzero on failure.
190+
*/
191+
static fih_ret boot_load_and_validate_current_image(struct boot_loader_state *state)
192+
{
193+
int rc;
194+
fih_ret fih_rc;
195+
uint32_t active_slot = state->slot_usage[BOOT_CURR_IMG(state)].active_slot;
196+
197+
if (active_slot == BOOT_SLOT_NONE) {
198+
FIH_RET(FIH_FAILURE);
199+
}
200+
201+
BOOT_LOG_INF("Loading image %d from slot %d", BOOT_CURR_IMG(state), active_slot);
202+
203+
#ifdef MCUBOOT_DIRECT_XIP
204+
rc = boot_rom_address_check(state);
205+
if (rc != 0) {
206+
FIH_RET(FIH_FAILURE);
207+
}
208+
#endif /* MCUBOOT_DIRECT_XIP */
209+
210+
#if defined(MCUBOOT_DIRECT_XIP_REVERT) || defined(MCUBOOT_RAM_LOAD_REVERT)
211+
rc = boot_select_or_erase(state);
212+
if (rc != 0) {
213+
FIH_RET(FIH_FAILURE);
214+
}
215+
#endif /* MCUBOOT_DIRECT_XIP_REVERT || MCUBOOT_RAM_LOAD_REVERT */
216+
217+
#ifdef MCUBOOT_RAM_LOAD
218+
/* Image is first loaded to RAM and authenticated there in order to
219+
* prevent TOCTOU attack during image copy. This could be applied
220+
* when loading images from external (untrusted) flash to internal
221+
* (trusted) RAM and image is authenticated before copying.
222+
*/
223+
rc = boot_load_image_to_sram(state);
224+
if (rc != 0) {
225+
/* Image cannot be ramloaded. */
226+
boot_remove_image_from_flash(state, active_slot);
227+
FIH_RET(FIH_FAILURE);
228+
}
229+
#endif /* MCUBOOT_RAM_LOAD */
230+
231+
FIH_CALL(boot_validate_slot, fih_rc, state, active_slot, NULL, 0);
232+
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
233+
/* Image is invalid. */
234+
#ifdef MCUBOOT_RAM_LOAD
235+
boot_remove_image_from_sram(state);
236+
#endif /* MCUBOOT_RAM_LOAD */
237+
FIH_RET(FIH_FAILURE);
238+
}
239+
240+
FIH_RET(FIH_SUCCESS);
241+
}
242+
243+
/**
244+
* Tries to load a slot for all the images with validation.
245+
*
246+
* @param state Boot loader status information.
247+
*
248+
* @return 0 on success; nonzero on failure.
249+
*/
250+
fih_ret boot_load_and_validate_images_hook(struct boot_loader_state *state)
251+
{
252+
uint32_t active_slot;
253+
int rc;
254+
fih_ret fih_rc;
255+
256+
while (true) {
257+
#if (BOOT_IMAGE_NUMBER > 1)
258+
BOOT_CURR_IMG(state) = MCUBOOT_MANIFEST_IMAGE_NUMBER;
259+
#endif
260+
rc = BOOT_HOOK_FIND_SLOT_CALL(boot_find_next_slot_hook, BOOT_HOOK_REGULAR, state,
261+
BOOT_CURR_IMG(state), &active_slot);
262+
if (rc == BOOT_HOOK_REGULAR) {
263+
active_slot = find_slot_with_highest_version(state);
264+
}
265+
if (active_slot == BOOT_SLOT_NONE) {
266+
BOOT_LOG_INF("No more manifest slots available");
267+
FIH_RET(FIH_FAILURE);
268+
}
269+
270+
/* Save the number of the active manifest slot. */
271+
state->slot_usage[BOOT_CURR_IMG(state)].active_slot = active_slot;
272+
273+
FIH_CALL(boot_load_and_validate_current_image, fih_rc, state);
274+
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
275+
state->slot_usage[MCUBOOT_MANIFEST_IMAGE_NUMBER]
276+
.slot_available[active_slot] = false;
277+
state->slot_usage[MCUBOOT_MANIFEST_IMAGE_NUMBER].active_slot =
278+
BOOT_SLOT_NONE;
279+
BOOT_LOG_INF("No valid manifest in slot %d", active_slot);
280+
continue;
281+
}
282+
283+
BOOT_LOG_INF("Try to validate images using manifest in slot %d", active_slot);
284+
285+
#if BOOT_IMAGE_NUMBER > 1
286+
/* Go over all other images and try to load one */
287+
IMAGES_ITER(BOOT_CURR_IMG(state))
288+
{
289+
/* Skip the image with manifest - it's been already verified. */
290+
if (BOOT_CURR_IMG(state) == MCUBOOT_MANIFEST_IMAGE_NUMBER) {
291+
continue;
292+
}
293+
294+
/* Check if there is a matching slot available. */
295+
if (!state->slot_usage[BOOT_CURR_IMG(state)].slot_available[active_slot]) {
296+
/* Invalidate manifest */
297+
FIH_SET(fih_rc, FIH_FAILURE);
298+
break;
299+
}
300+
301+
/* Save the number of the active slot. */
302+
state->slot_usage[BOOT_CURR_IMG(state)].active_slot = active_slot;
303+
304+
FIH_CALL(boot_load_and_validate_current_image, fih_rc, state);
305+
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
306+
state->slot_usage[BOOT_CURR_IMG(state)]
307+
.slot_available[active_slot] = false;
308+
state->slot_usage[BOOT_CURR_IMG(state)].active_slot =
309+
BOOT_SLOT_NONE;
310+
/* Invalidate manifest */
311+
break;
312+
}
313+
}
314+
#endif
315+
316+
if (FIH_EQ(fih_rc, FIH_SUCCESS)) {
317+
/* All images have been loaded and validated successfully. */
318+
break;
319+
}
320+
321+
BOOT_LOG_DBG("Manifest in slot %d is invalid", active_slot);
322+
323+
/* Invalidate manifest */
324+
state->slot_usage[MCUBOOT_MANIFEST_IMAGE_NUMBER].slot_available[active_slot] =
325+
false;
326+
state->slot_usage[MCUBOOT_MANIFEST_IMAGE_NUMBER].active_slot = BOOT_SLOT_NONE;
327+
#ifdef MCUBOOT_RAM_LOAD
328+
BOOT_CURR_IMG(state) = MCUBOOT_MANIFEST_IMAGE_NUMBER;
329+
boot_remove_image_from_sram(state);
330+
#endif /* MCUBOOT_RAM_LOAD */
331+
}
332+
333+
FIH_RET(FIH_SUCCESS);
334+
}

west.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ manifest:
128128
compare-by-default: true
129129
- name: mcuboot
130130
repo-path: sdk-mcuboot
131-
revision: 6256d9f6df644231fd196c94a5a92f23c1ad24d4
131+
revision: pull/551/head
132132
path: bootloader/mcuboot
133133
- name: qcbor
134134
url: https://github.com/laurencelundblade/QCBOR

0 commit comments

Comments
 (0)