DEV Community

Ripan Deuri
Ripan Deuri

Posted on

MCUboot: Secure Boot in Zephyr

MCUboot is an open-source secure bootloader designed for microcontrollers.

It ensures:

  • Only authentic and verified firmware runs (via digital signatures).
  • Fail-safe firmware upgrades (via slot-based copy and swap mechanisms).
  • Rollback protection (to prevent downgrades to vulnerable firmware).

High Level Boot Flow

When the device powers up, MCUboot runs before application. It decides which image (primary or secondary) should be booted, and whether an update needs to be applied.

Here’s a high-level view:

+------------------------+
|   MCU Reset Vector     |
+------------------------+
           |
           v
+------------------------+
|   MCUboot (bootloader) |
+------------------------+
     |         |
     |         +--> Secondary Slot (new image)
     |
     v
+------------------------+
|   Primary Slot         |
|   (Active image)       |
+------------------------+
           |
           v
+------------------------+
|   App main()           |
+------------------------+
Enter fullscreen mode Exit fullscreen mode

MCUboot performs the following:

  • Initializes flash, crypto, and image metadata.
  • Reads image headers and metadata (TLVs).
  • Determines if a swap or overwrite is needed.
  • Optionally performs image copy/swap.
  • Verifies signature of the image.
  • Jumps to application.

Image Layout

MCUboot application image has a header and TLV metadata. The layout in flash looks like this:

+-----------------------------------------------------------+
| Image Header (512 bytes, variable)                        |
+-----------------------------------------------------------+
| Application Binary Code                                   |
+-----------------------------------------------------------+
| TLV Info Header                                           |
+-----------------------------------------------------------+
| TLV Entries (Hash, Signature, KeyHash, etc.)              |
+-----------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

The TLVs may include:

  • SHA256 hash of the image
  • Signature (ECDSA or RSA)
  • Key hash
  • Custom TLVs (version, etc.)

Protected TLVs cannot be altered without invalidating the signature.

Flash Partition Layout

The default flash layout of STM32F411RE Nucleo board is:

+-------------------------------+ 0x08000000
| Bootloader (MCUboot)          | 16KB - Sector 0
+-------------------------------+ 0x08004000
| Bootloader (MCUboot)          | 16KB - Sector 1
+-------------------------------+ 0x08008000
| Bootloader (MCUboot)          | 16KB - Sector 2
+-------------------------------+ 0x0800C000
| Bootloader (MCUboot)          | 16KB - Sector 3
+-------------------------------+ 0x08010000
| Unused                        | 64KB - Sector 4
+-------------------------------+ 0x08020000
| Primary Slot (Slot 0)         | 128KB - Sector 5
+-------------------------------+ 0x08040000
| Secondary Slot (Slot 1)       | 128KB - Sector 6
+-------------------------------+ 0x08060000
| Scratch Area                  | 128KB - Sector 7
+-------------------------------+ 0x08080000 (end of flash)
Enter fullscreen mode Exit fullscreen mode

Image Upgrade

MCUboot support three main upgrade methods -

  • Overwrite: The new image from the secondary slot overwrites the primary image directly.
    • Simple, less metadata
    • No fail safe - if power fails mid copy, device may brick
  • Swap Using Scratch: A small scratch area is used to temporarily store sectors during image swap.
    • Complex - metadata tracks progress
    • Allows resumable swaps after power loss
    • Fail safe
    • Needs extra scratch area
  • Swap Using Move (No Scratch): Sector wise direct swap between slots
    • Require homogeneous sectors between slots
    • Primary slot needs an additional sector for swapping
    • Not ideal for heterogeneous sector sizes

Signature Verification

MCUboot verifies the image signature after the swap or overwrite using the public keys embedded at build time.

When MCUboot decides to boot an image (either in slot 0 or after swap from slot 1):

  • It computes the SHA-256 hash of the firmware region (excluding TLVs).
  • It locates the signature TLV and key hash TLV.
  • It compares the key hash against the public key(s) compiled into MCUboot.
  • If a match is found, it verifies the signature.

Version Management and Rollback Protection

App image carries a semantic version major.minor.revision.build encoded in its header and TLV:

Image Version: 1.2.0+5
Enter fullscreen mode Exit fullscreen mode

When MCUboot detects a new image in the secondary slot, it checks the version before upgrading. MCUboot can be built to accept only the newer firmware version. This prevents accidental or malicious rollbacks to older, possibly vulnerable firmware.

Rollback protection ensures that once the device boots a confirmed image, it will never revert to an older or unverified one. After a successful upgrade, the application must confirm the image by updating the boot status flag in flash. If the device reboots before confirmation (e.g., crash or power loss), MCUboot will rollback to the previous image during the next boot.

Setup MCUboot for STM32F411RE Nucleo

Zephyr DTS for nucleo_f411re can be found at

boards/st/nucleo_f411re/nucleo_f411re.dts

Finally the complete DTS after building (source + overlay)

build/zephyr/zephyr.dts

Following partitions are defined in the DTS by default for the board

Partition           Label Name      Offset      Size              Final Offset (Hex)
boot_partition      mcuboot         0x0         0x10000 (64 KB)   0x08000000
slot0_partition     image-0         0x20000     0x20000 (128 KB)  0x08020000
slot1_partition     image-1         0x40000     0x20000 (128 KB)  0x08040000
scratch_partition   image-scratch   0x60000     0x20000 (128 KB)  0x08060000
Enter fullscreen mode Exit fullscreen mode

Sector sizes from STM32F401RE reference manual

| Block       | Name     | Block Base address          | Size (Kbytes) |
|-------------|----------|-----------------------------|---------------|
|             | Sector 0 | 0x0800 0000 - 0x0800 3FFF   | 16            |
|             | Sector 1 | 0x0800 4000 - 0x0800 7FFF   | 16            |
| Main Memory | Sector 2 | 0x0800 8000 - 0x0800 BFFF   | 16            |
|             | Sector 3 | 0x0800 C000 - 0x0800 FFFF   | 16            |
|             | Sector 4 | 0x0801 0000 - 0x0801 FFFF   | 64            |
|             | Sector 5 | 0x0802 0000 - 0x0803 FFFF   | 128           |
|             | Sector 6 | 0x0804 0000 - 0x0805 FFFF   | 128           |
|             | Sector 7 | 0x0806 0000 - 0x0807 FFFF   | 128           
Enter fullscreen mode Exit fullscreen mode

Partitions are mapped as follows:

| Partition  | Sector  | Sector size (Kbytes)|
|------------|---------|---------------------|
| boot       | 0-3     | 16                  |
| slot0      | 5       | 128                 |
| slot1      | 6       | 128                 |
| scratch    | 7       | 128                 |
Enter fullscreen mode Exit fullscreen mode

By default, the DTS describe code-partition as

zephyr,code-partition \= \&slot0\_partition;

Below DTS overlay is required for mcuboot to start the code from 0x0800\_0000

/ {
    chosen {
        zephyr,code-partition = &boot_partition;
    };
};
Enter fullscreen mode Exit fullscreen mode

Complete script to build mcuboot and app image

  • Build mcuboot -> zephyr.bin [mcuboot]
    • swap using scratch
    • no signature
  • Build app -> zephyr.bin [app]
  • Sign app - add image header and TLVs -> zephyr.signed.bin [app]
echo "=== Building mcuboot ==="
echo

pushd "${MCUBOOT_ZEPHYR_DIR}" >/dev/null

west build -b "${BOARD}" -d "${MCUBOOT_BUILD_DIR}" -p auto -- \
    -DDTC_OVERLAY_FILE="${DTS_MCUBOOT_OVERLAY_NUCLEO_F401RE}" \
    -DCONFIG_BOOT_MAX_IMG_SECTORS=8 \
    -DCONFIG_BOOT_MAX_IMG_SECTORS_AUTO=n \
    -DCONFIG_BOOT_SIGNATURE_TYPE_NONE=y \
    -DCONFIG_MCUBOOT_LOG_LEVEL_DBG=y

if [[ ! -f "${MCUBOOT_BIN}" ]]; then
  echo "ERROR: mcuboot binary not found at ${MCUBOOT_BIN}"
  popd >/dev/null
  exit 1
fi

popd >/dev/null

echo "+++ mcuboot built: ${MCUBOOT_BIN} +++"

echo "=== Building app ==="
echo

west build -b ${BOARD} . -d build-app -p auto -- \
  -DCONFIG_BOOTLOADER_MCUBOOT=y \
  -DCONFIG_MCUBOOT_GENERATE_UNSIGNED_IMAGE=y

IMGTOOL_PY="${MCUBOOT_DIR}/scripts/imgtool.py"

west sign -d build-app -t imgtool -p ${IMGTOOL_PY}  -- \
  --pad \
  --slot-size 0x20000 \
  --header-size 0x200 \
  --align 4
Enter fullscreen mode Exit fullscreen mode

Top comments (0)