Skip to content

Conversation

@fabian18
Copy link
Contributor

@fabian18 fabian18 commented Oct 29, 2025

Contribution description

Introducing riotboot_hdr_v2.

We want to test boot an image and have an automatic revert to a previous image if a new image fails to boot.
For example due to an unexpected endless loop or compromised firmware or kernel panic.

This adds a flags field to the header to encode an image state and a number of boot attempts.
The watchdog is started in the bootloader. An image has to confirm that it booted successfully to not be labeled as DISMISSED by the bootloader.

Testing procedure

I test with: #21565 because this is just more convenient for me.

Run the SUIT example.
Run the SUIT coap server.

Flash with new header and watchdog timer

Auto Image Activation

riotboot_hdr_v2 + riotboot_wdt

USEMODULE+="riotboot_hdr_v2 riotboot_wdt" \
DEVELHELP=1 USE_ETHOS=0 SUIT_COAP_SERVER_IFACE=enp1s0f0 \
BOARD=same54-xpro make -C examples/advanced/suit_update/ clean flash term
2025-11-07 16:49:19,583 # riotboot_hdr
2025-11-07 16:49:19,589 # Usage: riotboot_hdr [<slot> [activate|deactivate|confirm|dismiss]]
> riotboot_hdr 0
2025-11-07 16:49:21,896 # riotboot_hdr 0
2025-11-07 16:49:21,899 # Image magic_number: 0x54304952
2025-11-07 16:49:21,901 # Image Version: 0x690e140c
2025-11-07 16:49:21,904 # Image start address: 0x00004400
2025-11-07 16:49:21,906 # Header chksum: 0x703b5e9d
2025-11-07 16:49:21,908 # Image flags: 0xffffff8f
2025-11-07 16:49:21,909 #

Note that the image state is confirmed to boot.

Compile and push an update using riotboot_hdr_v2 + riotboot_wdt.

USEMODULE+="riotboot_hdr_v2 riotboot_wdt" \
DEVELHELP=1 USE_ETHOS=0 SUIT_COAP_SERVER_IFACE=enp1s0f0 \
BOARD=same54-xpro make -C examples/advanced/suit_update/ clean suit/publish 

Fetch the update.

suit fetch

2025-11-07 16:53:22,630 # suit fetch
2025-11-07 16:53:22,632 # suit_worker: started.
2025-11-07 16:53:22,640 # suit_worker: downloading "coap://[2001:db8::1]/fw/suit_update/same54-xpro/riot.suit.latest.bin"
> 2025-11-07 16:53:22,655 # suit_worker: got manifest with size 485
2025-11-07 16:53:22,659 # suit: verifying manifest signature
2025-11-07 16:53:23,801 # suit: validated manifest version
2025-11-07 16:53:23,806 # Manifest seq_no: 1762530734, highest available: 1762530316
2025-11-07 16:53:23,809 # suit: validated sequence number
2025-11-07 16:53:23,811 # Formatted component name: 
2025-11-07 16:53:23,816 # Comparing manifest offset 4000 with other slot offset
2025-11-07 16:53:23,818 # offset does not match
2025-11-07 16:53:23,823 # Comparing manifest offset 82000 with other slot offset
2025-11-07 16:53:23,825 # validating vendor ID
2025-11-07 16:53:23,834 # Comparing 547d0d74-6d3a-5a92-9662-4881afd9407b to 547d0d74-6d3a-5a92-9662-4881afd9407b from manifest
2025-11-07 16:53:23,836 # validating vendor ID: OK
2025-11-07 16:53:23,838 # validating class id
2025-11-07 16:53:23,846 # Comparing 50244518-6a7c-5ce7-932b-88b318336c82 to 50244518-6a7c-5ce7-932b-88b318336c82 from manifest
2025-11-07 16:53:23,849 # validating class id: OK
2025-11-07 16:53:23,853 # Comparing manifest offset 4000 with other slot offset
2025-11-07 16:53:23,855 # offset does not match
2025-11-07 16:53:23,860 # Comparing manifest offset 82000 with other slot offset
2025-11-07 16:53:23,862 # SUIT policy check OK.
2025-11-07 16:53:23,864 # Formatted component name: 
2025-11-07 16:53:23,870 # riotboot_flashwrite: initializing update to target slot 1
2025-11-07 16:53:23,910 # Fetching firmware |█████████████████████████| 100%
2025-11-07 16:53:46,380 # Finalizing payload store
2025-11-07 16:53:46,382 # Verifying image digest
2025-11-07 16:53:46,386 # Starting digest verification against image
2025-11-07 16:53:46,494 # Install correct payload
2025-11-07 16:53:46,496 # Verified installed payload
2025-11-07 16:53:46,498 # Verifying image digest
2025-11-07 16:53:46,502 # Starting digest verification against image
2025-11-07 16:53:46,611 # Verified installed payload
2025-11-07 16:53:46,613 # Image magic_number: 0x54304952
2025-11-07 16:53:46,616 # Image Version: 0x690e15ae
2025-11-07 16:53:46,619 # Image start address: 0x00082400
2025-11-07 16:53:46,621 # Header chksum: 0x36cb4047
2025-11-07 16:53:46,623 # Image flags: 0xffffffff

Check that the otehr slot is booted and the image state is "confirmed" and it booted after 1 attempts.

2025-11-07 17:02:23,499 # riotboot_hdr 1
2025-11-07 17:02:23,501 # Image magic_number: 0x54304952
2025-11-07 17:02:23,504 # Image Version: 0x690e1796
2025-11-07 17:02:23,507 # Image start address: 0x00082400
2025-11-07 17:02:23,509 # Header chksum: 0x3e6b422f
2025-11-07 17:02:23,511 # Image flags: 0xffffff8e

No Automatic Image Activation

Flash

DISABLE_MODULE+=riotboot_hdr_auto_activate USEMODULE+="riotboot_hdr_v2 riotboot_wdt" \
DEVELHELP=1 USE_ETHOS=0 SUIT_COAP_SERVER_IFACE=enp1s0f0 \
BOARD=same54-xpro make -C examples/advanced/suit_update/ clean flash term

Push update

DISABLE_MODULE+=riotboot_hdr_auto_activate USEMODULE+="riotboot_hdr_v2 riotboot_wdt" DEVELHELP=1 USE_ETHOS=0 SUIT_COAP_SERVER_IFACE=enp1s0f0 BOARD=same54-xpro make -C examples/advanced/suit_update/ clean suit/publish 

Fetch update

> 2025-11-07 17:32:13,178 # suit_worker: got manifest with size 485
2025-11-07 17:32:13,181 # suit: verifying manifest signature
2025-11-07 17:32:14,323 # suit: validated manifest version
2025-11-07 17:32:14,328 # Manifest seq_no: 1762532965, highest available: 1762532813
2025-11-07 17:32:14,331 # suit: validated sequence number
2025-11-07 17:32:14,334 # Formatted component name: 
2025-11-07 17:32:14,338 # Comparing manifest offset 4000 with other slot offset
2025-11-07 17:32:14,340 # offset does not match
2025-11-07 17:32:14,345 # Comparing manifest offset 82000 with other slot offset
2025-11-07 17:32:14,347 # validating vendor ID
2025-11-07 17:32:14,356 # Comparing 547d0d74-6d3a-5a92-9662-4881afd9407b to 547d0d74-6d3a-5a92-9662-4881afd9407b from manifest
2025-11-07 17:32:14,358 # validating vendor ID: OK
2025-11-07 17:32:14,360 # validating class id
2025-11-07 17:32:14,369 # Comparing 50244518-6a7c-5ce7-932b-88b318336c82 to 50244518-6a7c-5ce7-932b-88b318336c82 from manifest
2025-11-07 17:32:14,371 # validating class id: OK
2025-11-07 17:32:14,376 # Comparing manifest offset 4000 with other slot offset
2025-11-07 17:32:14,378 # offset does not match
2025-11-07 17:32:14,382 # Comparing manifest offset 82000 with other slot offset
2025-11-07 17:32:14,384 # SUIT policy check OK.
2025-11-07 17:32:14,387 # Formatted component name: 
2025-11-07 17:32:14,392 # riotboot_flashwrite: initializing update to target slot 1
2025-11-07 17:32:14,431 # Fetching firmware |█████████████████████████| 100%
2025-11-07 17:32:36,822 # Finalizing payload store
2025-11-07 17:32:36,824 # Verifying image digest
2025-11-07 17:32:36,828 # Starting digest verification against image
2025-11-07 17:32:36,936 # Install correct payload
2025-11-07 17:32:36,939 # Verified installed payload
2025-11-07 17:32:36,941 # Verifying image digest
2025-11-07 17:32:36,944 # Starting digest verification against image
2025-11-07 17:32:37,053 # Verified installed payload
2025-11-07 17:32:37,056 # Image magic_number: 0x54304952
2025-11-07 17:32:37,058 # Image Version: 0x690e1e65
2025-11-07 17:32:37,061 # Image start address: 0x00082400
2025-11-07 17:32:37,063 # Header chksum: 0x59a748fe
2025-11-07 17:32:37,065 # Image flags: 0xffffffff
2025-11-07 17:32:37,065 # 
2025-11-07 17:32:38,069 # suit_worker: deactivating target slot
2025-11-07 17:32:38,071 # suit_worker: update successful
2025-11-07 17:32:38,074 # suit_worker: rebooting...
2025-11-07 17:32:38,135 # main(): This is RIOT! (Version: 2025.10-devel-263-ga418b-pr/riotboot_hdr_v2_test)
2025-11-07 17:32:38,138 # RIOT SUIT update example application
2025-11-07 17:32:38,140 # Running from slot 0
2025-11-07 17:32:38,142 # Unknown command �I ��Q�
2025-11-07 17:32:38,144 # Starting the shell

Note after reboot it has booted slot 0 again because slot 1 is not activated.
Activate slot 1:

> riotboot_hdr 1 activate
2025-11-07 17:34:17,491 # riotboot_hdr 1 activate
> riotboot_hdr 1
2025-11-07 17:34:21,988 # riotboot_hdr 1
2025-11-07 17:34:21,990 # Image magic_number: 0x54304952
2025-11-07 17:34:21,993 # Image Version: 0x690e1e65
2025-11-07 17:34:21,996 # Image start address: 0x00082400
2025-11-07 17:34:21,998 # Header chksum: 0x59a748fe
2025-11-07 17:34:22,000 # Image flags: 0xffffffcf
2025-11-07 17:34:22,000 # 

Reboot and it will boot slot 1:

> reboot
2025-11-07 17:34:53,861 # reboot
2025-11-07 17:34:53,922 # main(): This is RIOT! (Version: 2025.10-devel-263-ga418b-pr/riotboot_hdr_v2_test)
2025-11-07 17:34:53,926 # RIOT SUIT update example application
2025-11-07 17:34:53,927 # Running from slot 1
2025-11-07 17:34:53,930 # Unknown command �I ��Q�
2025-11-07 17:34:53,931 # Starting the shell

Auto Revert

Flash riotboot_hdr_v2 + riotboot_wdt

USEMODULE+="riotboot_hdr_v2 riotboot_wdt" \
DEVELHELP=1 USE_ETHOS=0 SUIT_COAP_SERVER_IFACE=enp1s0f0 \
BOARD=same54-xpro make -C examples/advanced/suit_update/ clean flash term

Modify the firmware and include a panic() before the image is confirmed.

#if IS_USED(MODULE_RIOTBOOT)
    debug_mon_default();
    if (IS_USED(MODULE_RIOTBOOT_HDR_AUTO_CONFIRM)) {
        riotboot_slot_confirm();
        if (IS_USED(MODULE_RIOTBOOT_WDT)) {
            riotboot_wdt_stop();
        }
    }
#endif

Push bad image update with DEVELHELP=0 to get #define CONFIG_CORE_REBOOT_ON_PANIC (1)

USEMODULE+="riotboot_hdr_v2 riotboot_wdt" DEVELHELP=0 USE_ETHOS=0 SUIT_COAP_SERVER_IFACE=enp1s0f0 BOARD=same54-xpro make -C examples/advanced/suit_update/ clean suit/publish 

Fetch manifest and observe that after 3 reboots the old image is successfully booted and the other image is dismissed.

2025-11-12 21:20:57,458 # suit_worker: activating target slot
2025-11-12 21:20:57,461 # suit_worker: update successful
2025-11-12 21:20:57,463 # suit_worker: rebooting...
2025-11-12 21:20:57,524 # main(): This is RIOT! (Version: 2025.10-devel-257-gd66d7-pr/riotboot_hdr_v2_test)
2025-11-12 21:20:57,526 # *** RIOT kernel panic:
2025-11-12 21:20:57,527 # DEBUG MON HANDLER
2025-11-12 21:20:57,527 # 
2025-11-12 21:20:57,529 # *** rebooting...
2025-11-12 21:20:57,529 # 
2025-11-12 21:20:57,590 # main(): This is RIOT! (Version: 2025.10-devel-257-gd66d7-pr/riotboot_hdr_v2_test)
2025-11-12 21:20:57,592 # *** RIOT kernel panic:
2025-11-12 21:20:57,594 # DEBUG MON HANDLER
2025-11-12 21:20:57,594 # 
2025-11-12 21:20:57,595 # *** rebooting...
2025-11-12 21:20:57,595 # 
2025-11-12 21:20:57,657 # main(): This is RIOT! (Version: 2025.10-devel-257-gd66d7-pr/riotboot_hdr_v2_test)
2025-11-12 21:20:57,659 # *** RIOT kernel panic:
2025-11-12 21:20:57,661 # DEBUG MON HANDLER
2025-11-12 21:20:57,661 # 
2025-11-12 21:20:57,662 # *** rebooting...
2025-11-12 21:20:57,662 # 
2025-11-12 21:20:57,724 # main(): This is RIOT! (Version: 2025.10-devel-257-gd66d7-pr/riotboot_hdr_v2_test)
2025-11-12 21:20:57,727 # RIOT SUIT update example application
2025-11-12 21:20:57,729 # Running from slot 1
2025-11-12 21:20:57,731 # Unknown command �I ��Q�
2025-11-12 21:20:57,733 # Starting the shell
> riotboot_hdr 0
2025-11-12 21:21:17,505 # riotboot_hdr 0
2025-11-12 21:21:17,508 # Image magic_number: 0x54304952
2025-11-12 21:21:17,511 # Image Version: 0x6914ebcd
2025-11-12 21:21:17,513 # Image start address: 0x00004400
2025-11-12 21:21:17,516 # Header chksum: 0xcf543665
2025-11-12 21:21:17,518 # Image flags: 0xffffff08
2025-11-12 21:21:17,518 # 

Issues/PRs references

@github-actions github-actions bot added Area: core Area: RIOT kernel. Handle PRs marked with this with care! Area: build system Area: Build system Area: tools Area: Supplementary tools Area: OTA Area: Over-the-air updates Area: sys Area: System labels Oct 29, 2025
@fabian18 fabian18 changed the title sys/riotboot: riotboot head v2 sys/riotboot: riotboot header v2 Oct 29, 2025
@fabian18
Copy link
Contributor Author

I first wanted that the flags are also covered by the checksum. But this required rewriting of the checksum and this required a page erase of the current firmware and this made the program stall.

Copy link
Contributor

@crasbe crasbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some preliminary high-level styling related comments. The static test also has some warnings left.

core/lib/init.c Outdated
#include "stdio_base.h"

#if IS_USED(MODULE_RIOTBOOT)
#include "riotboot/wdt.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "riotboot/wdt.h"
# include "riotboot/wdt.h"

Comment on lines 52 to 57
#if defined(MODULE_RIOTBOOT_HDR_V2)
#define RIOTBOOT_MAGIC RIOTBOOT_MAGIC_V2
#else
#define RIOTBOOT_MAGIC RIOTBOOT_MAGIC_V1
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if defined(MODULE_RIOTBOOT_HDR_V2)
#define RIOTBOOT_MAGIC RIOTBOOT_MAGIC_V2
#else
#define RIOTBOOT_MAGIC RIOTBOOT_MAGIC_V1
#endif
#if defined(MODULE_RIOTBOOT_HDR_V2)
# define RIOTBOOT_MAGIC RIOTBOOT_MAGIC_V2
#else
# define RIOTBOOT_MAGIC RIOTBOOT_MAGIC_V1
#endif

Comment on lines 77 to 81
#ifndef CONFIG_RIOTBOOT_MAX_ATTEMPTS
#define CONFIG_RIOTBOOT_MAX_ATTEMPTS 3u
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#ifndef CONFIG_RIOTBOOT_MAX_ATTEMPTS
#define CONFIG_RIOTBOOT_MAX_ATTEMPTS 3u
#endif
#ifndef CONFIG_RIOTBOOT_MAX_ATTEMPTS
# define CONFIG_RIOTBOOT_MAX_ATTEMPTS 3u
#endif

* The timeout is doubled on each unsuccessful attempt.
*/
#ifndef CONFIG_RIOTBOOT_WDT_TIMEOUT_MSEC
#define CONFIG_RIOTBOOT_WDT_TIMEOUT_MSEC 4000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define CONFIG_RIOTBOOT_WDT_TIMEOUT_MSEC 4000
# define CONFIG_RIOTBOOT_WDT_TIMEOUT_MSEC 4000

Comment on lines 52 to 58
switch (riotboot_hdr->v1.magic_number) {
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.version;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.version;
default:
return NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
switch (riotboot_hdr->v1.magic_number) {
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.version;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.version;
default:
return NULL;
switch (riotboot_hdr->v1.magic_number) {
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.version;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.version;
default:
return NULL;

As per our Coding Convention: https://github.com/RIOT-OS/RIOT/blob/master/CODING_CONVENTIONS.md#indentation-and-braces

Comment on lines 65 to 70
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.start_addr;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.start_addr;
default:
return NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.start_addr;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.start_addr;
default:
return NULL;
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.start_addr;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.start_addr;
default:
return NULL;

Comment on lines 85 to 90
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.chksum;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.chksum;
default:
return NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.chksum;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.chksum;
default:
return NULL;
case RIOTBOOT_MAGIC_V1:
return &riotboot_hdr->v1.chksum;
case RIOTBOOT_MAGIC_V2:
return &riotboot_hdr->v2.chksum;
default:
return NULL;

uint32_t riotboot_hdr_get_flags(const riotboot_hdr_t *riotboot_hdr);

/**
* @brief Calculate header checksum
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief Calculate header checksum
* @brief Getter for the header checksum

If I understand the code correctly, it does not calculate the checksum when calling this function, right?

@crasbe crasbe added the Type: new feature The issue requests / The PR implemements a new feature for RIOT label Oct 29, 2025
@mguetschow
Copy link
Contributor

Are the riotboot images somehow compatible with the SUIT ecosystem? If not, wouldn't it make sense to converge towards that instead of introducing yet another custom format?

@fabian18
Copy link
Contributor Author

What contradiction do you see in SUIT? From a SUIT perspective there is only binary payload.
You could say that the header does not have to be shipped with an image because it could be generated by a currently running image. This would not change the fact that there is a header and that I would like to encode an image state.

@mguetschow
Copy link
Contributor

Disclaimer: I haven't looked much into SUIT. But from my understanding, your proposed riotboot header includes metadata information about the image, that I would have expected in a SUIT manifest such as https://datatracker.ietf.org/doc/draft-ietf-suit-manifest/.

But it can well be that that metadata is disjoint from what would be carried in a SUIT manifest, in that case please just disregard my comment.

@fabian18
Copy link
Contributor Author

fabian18 commented Nov 4, 2025

SUIT manifest can only hold static info about the image because it is used only during installation as far as I know. This PR adds flags which change after an installation

@github-actions github-actions bot added the Area: examples Area: Example Applications label Nov 6, 2025
@fabian18 fabian18 marked this pull request as ready for review November 6, 2025 20:28
@crasbe crasbe added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Nov 12, 2025
@riot-ci
Copy link

riot-ci commented Nov 12, 2025

Murdock results

FAILED

bf983c9 sys/riotboot: riotboot head v2

Success Failures Total Runtime
663 0 9972 01m:56s

Artifacts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: build system Area: Build system Area: core Area: RIOT kernel. Handle PRs marked with this with care! Area: examples Area: Example Applications Area: OTA Area: Over-the-air updates Area: sys Area: System Area: tools Area: Supplementary tools CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants