Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CONFIG_SINGLE_APPLICATION_SLOT should test and confirm #1931

Open
JPHutchins opened this issue Mar 30, 2024 · 23 comments
Open

CONFIG_SINGLE_APPLICATION_SLOT should test and confirm #1931

JPHutchins opened this issue Mar 30, 2024 · 23 comments

Comments

@JPHutchins
Copy link

JPHutchins commented Mar 30, 2024

For the Zephyr implementation, there is a separate file single_loader.c. I suppose my first question is why the single application slot configuration is treated separately from the multi-slot configuration at all?

As it stands, it is possible to brick a board using MCUBoot by loading a bad application to the application slot. This is because the single_loader.c implementation does not utilize the test (Swap Info) and confirm (Image OK) flags provided by the Image Trailer spec:

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ~                                                               ~
    ~    Swap status (BOOT_MAX_IMG_SECTORS * min-write-size * 3)    ~
    ~                                                               ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                 Encryption key 0 (16 octets) [*]              |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    0xff padding as needed                     |
    |  (BOOT_MAX_ALIGN minus 16 octets from Encryption key 0) [*]   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                 Encryption key 1 (16 octets) [*]              |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    0xff padding as needed                     |
    |  (BOOT_MAX_ALIGN minus 16 octets from Encryption key 1) [*]   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                      Swap size (4 octets)                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    0xff padding as needed                     |
    |        (BOOT_MAX_ALIGN minus 4 octets from Swap size)         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Swap info   |  0xff padding (BOOT_MAX_ALIGN minus 1 octet)  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Copy done   |  0xff padding (BOOT_MAX_ALIGN minus 1 octet)  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Image OK    |  0xff padding (BOOT_MAX_ALIGN minus 1 octet)  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    0xff padding as needed                     |
    |         (BOOT_MAX_ALIGN minus 16 octets from MAGIC)           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                       MAGIC (16 octets)                       |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Specifically, when MCUBoot has completed a single slot application upgrade via SMP, USB DFU, or other, it should conform to the same "test" and "confirm" routine as a multi-slot configuration:

  1. MCUBoot completes upgrade of the application
  2. MCUBoot marks the image for test, setting Swap Info to BOOT_SWAP_TYPE_TEST
  3. MCUBoot resets the system
  4. MCUBoot finds that the application marked for test is already in the primary slot (single slot config), clears the test flag, and loads the application
  5. On the next reset, MCUBoot does not see the test flag, so it checks for Image OK. If it does not find Image OK then it erases the application and goes into serial recovery

The routine described above will prevent an unrecoverable bootloop so long as the user confirms the test image responsibly from within the application.

@nordicjm
Copy link
Collaborator

nordicjm commented Apr 2, 2024

It is designed this way, single application has a single slot, there is nothing to confirm or test, you enter serial recovery mode using e.g. a button or if the application is not valid and then load a new image. MCUboot serial recovery does not support image status out of the box and really has no need to in this case, it just adds additional flash usage for no reason

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 2, 2024

@nordicjm I think there is another dimension to that. While two slots are needed to ensure reliable fw updates practically mandatory for any loader app (or device may get bricked), one slot takes just half of the flash space that two slots do and sometimes it matters a lot.
I currently contemplate the scenario described here where I think two types of slots (single/double) used simultaneously would provide the best functionality but it seem that such scenario is not currently supported.

@nordicjm
Copy link
Collaborator

nordicjm commented Apr 2, 2024

@nordicjm I think there is another dimension to that. While two slots are needed to ensure reliable fw updates practically mandatory for any loader app (or device may get bricked), one slot takes just half of the space that two slots do and sometimes it matters a lot. I currently contemplate the scenario described here where I think two types of slots (single/double) used simultaneously would provide the best functionality but it seem that such scenario is not currently supported.

There already is a loader mode where the slot sizes can differ, you have a loader application (which can be immutable or can be updated if you use MCUboot serial recovery), if the loader conditions are met then MCUboot will boot the loader application, if not it will boot to the normal application

Example that uses it: https://github.com/thedjnK/net_loader a slim loader application runs which allows for loading images to the device over the network (UDP), once the device reboots it runs that image, upon an additional reboot it reverts back to the loader application

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 2, 2024

My loader has to use USB stick so it is not a trivial y-modem recovery app, likely will need updates and enhancements, hence it needs to be easily updatable. Essentially, it needs to be able to update itself the same way the main app is updated.
MCUboot serial recovery option used to update my loader app just is not good enough.

@nordicjm
Copy link
Collaborator

nordicjm commented Apr 2, 2024

My loader has to use USB stick so it is not a trivial y-modem recovery app, likely will need updates and enhancements, hence it needs to be easily updatable. Essentially, it needs to be able to update itself the same way the main app is updated. MCUboot serial recovery option used to update my loader app just is not good enough.

Then that's not going to work with mcuboot, either you have 1 single slot or 1 single slot with a loader, or you have 2 double slots, one image pair for the loader and one image pair for the application.

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 2, 2024

@nordicjm Unfortunately, that is what I thought but thank you for the confirmation.
I prepare myself to put together a custom loader app only loosely based on mcuboot to overcome this current constraint.

I think currently the only way it could be done without too much code changes is by using two separate sets of configs which will allow MCUboot to believe that my Secure Loader app using two slots is the only image to be loaded while Secure Loader itself would be configured with MCUBOOT_PRIMARY_ONLY, MCUBOOT_OVERWRITE_ONLY, MCUBOOT_ENC_IMAGES options in order to load the Main App (just one large, initially encrypted slot).
In another words, it would be like one mcuboot loading and jumping to another mcuboot with extended functionality.
In any case, it probably still will take a substantial effort to get this done.

Screenshot 2024-03-28 173607

@JPHutchins
Copy link
Author

you enter serial recovery mode using e.g. a button

Alas, many designers do not include a button on their products! If I am early in a project then I will always advocate for a button (or an exposed GPIO pad that can get shorted) as an "escape hatch" to get to the bootloader. Even then, it doesn't get included because we are talking about tiny wearables that need IP66 etc. And to be clear, these sorts of products don't even have a reliable (hardware) means of power cycling or resetting the MCU.

or if the application is not valid and then load a new image

This also does not work because "valid" simply means signed. Pleasantly this solves the problem of an interrupted upgrade. But, a "valid" FW can brick the device if it encounters an error that prevents user interaction. A resilient bootloader and DFU system should not assume that all software is free from bugs.

does not support image status out of the box and really has no need to in this case, it just adds additional flash usage for no reason

On the contrary, it fulfills the critical need of preventing a device from being bricked by a bad FW.

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 6, 2024

@nordicjm Just a thought: can MCUboot External Loader (MCUBOOT_EXT_LOADER) image be signed and initially encrypted?
If it can, than using it as my Main App might be a viable option and probably the simplest solution.

@nordicjm
Copy link
Collaborator

nordicjm commented Apr 8, 2024

you enter serial recovery mode using e.g. a button

Alas, many designers do not include a button on their products! If I am early in a project then I will always advocate for a button (or an exposed GPIO pad that can get shorted) as an "escape hatch" to get to the bootloader. Even then, it doesn't get included because we are talking about tiny wearables that need IP66 etc. And to be clear, these sorts of products don't even have a reliable (hardware) means of power cycling or resetting the MCU.

or if the application is not valid and then load a new image

This also does not work because "valid" simply means signed. Pleasantly this solves the problem of an interrupted upgrade. But, a "valid" FW can brick the device if it encounters an error that prevents user interaction. A resilient bootloader and DFU system should not assume that all software is free from bugs.

does not support image status out of the box and really has no need to in this case, it just adds additional flash usage for no reason

On the contrary, it fulfills the critical need of preventing a device from being bricked by a bad FW.

You're going to be in the exact same situation if the firmware confirms itself then crashes. If you don't have a dedicated means of entering the bootloader without the main application doing something then there is a chance the device bricks itself and no amount of tinkering is going to prevent that.

@nordicjm
Copy link
Collaborator

nordicjm commented Apr 8, 2024

@nordicjm Just a thought: can MCUboot External Loader (MCUBOOT_EXT_LOADER) image be signed and initially encrypted? If it can, than using it as my Main App might be a viable option and probably the simplest solution.

I don't know what you mean by MCUBOOT_EXT_LOADER. If you mean firmware loading, then the image itself as programmed with a hex cannot be encrypted no, I have not tested loading an encrypted file in this mode

@tdjastrzebski
Copy link

@nordicjm That is right. I analyzed the source code, in particular execute_loader() function, and it seems that external loader image cannot be encrypted.

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 8, 2024

@nordicjm

You're going to be in the exact same situation if the firmware confirms itself then crashes. If you don't have a dedicated means of entering the bootloader without the main application doing something then there is a chance the device bricks itself and no amount of tinkering is going to prevent that.

That is right, but just like pretty much anything else in life and in the universe I think this can just be seen in terms of probability.
Some risks levels are acceptable and some are not. E.g. the solution I depictured several posts above is not 100% safe, but I am perfectly ok with it. I am happy to allow for this little risk and get twice more app flash space in return. Not to mention that there are ways to mitigate it, e.g. confirm new "secure loader" only after it successfully loads new "main app" and this "main app" is up and running. Secure Loader can be prevented from erasing/damaging itself and allowed to write only to its secondary slot using proper memory protection. If/when I need to eliminate even such slim remaining risk, then I can still have an "external loader". After all it is a very small app, just five 8k sectors.

@tdjastrzebski
Copy link

@JPHutchins I realized that probably I do not fully understand what CONFIG_SINGLE_APPLICATION_SLOT option does.
Is it documented somewhere? Do I correctly assume that practically speaking it can allow for in-place decryption?

@nordicjm
Copy link
Collaborator

@JPHutchins I realized that probably I do not fully understand what CONFIG_SINGLE_APPLICATION_SLOT option does. Is it documented somewhere? Do I correctly assume that practically speaking it can allow for in-place decryption?

It means you only have a single slot for the application instead of 2, it means the application cannot update itself, the only way to update the application is by using the bootloader through serial recovery (serial/USB CDC only, no other transports like bluetooth etc.). You can uploade an encrypted image through MCUboot's serial recovery to the slot and it will be decrypted yes

@tdjastrzebski
Copy link

@JPHutchins Thank you, so I can use this approach in my scenario. Do you happen to know how to correctly prepare single-app image with imagetool? I think there is no dedicated options. Are those the correct options to be used?
--overwrite-only --primary-only --align 1 --header-size 0x400 --pad-header

@nordicjm
Copy link
Collaborator

You just generate it normally, --align 4 --header-size 0x400 --pad-header

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 15, 2024

@JPHutchins Thank you. The only remaining dilemma I have to solve is whether it is more practical to modify MCUboot so that it can install images both by swapping (moving) slots as well as a performing in-place decryption, or implement the latter functionality in my 'secure loader' leaving MCUboot intact.

@nordicjm
Copy link
Collaborator

@JPHutchins Thank you. The only remaining dilemma I have to solve is whether it is more practical to modify MCUboot so that it can install images both by swapping (moving) slots as well as a performing in-place decryption, or implement the latter functionality in my 'secure loader' leaving MCUboot intact.

Not sure what you mean, if you have a single slot then the image is transferred entirely and them MCUboot will decrypt it. If you have 2 slots then the secondary slot will hold an encrypted image, if the images are swapped then the secondary image will be decrypted and place into the primary slot and the primary slot image will be encrypted and placed into the secondary slot image

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 15, 2024

@JPHutchins The problem is that my solution needs to use both strategies (single and double slot) simultaneously.
'secure loader' app needs to use two slots to ensure maximum robust updates while the 'main app' needs just one slot taking the full remaining flash space.
I do not think that the current original MCUboot implementation can handle that, although I could be wrong.

@nordicjm
Copy link
Collaborator

@JPHutchins The problem is that my solution needs to use both strategies (single and double slot) simultaneously. 'secure loader' app needs to use two slots to ensure robust updates while the 'main app' needs just one slot taking the full remaining flash space. I do not think that the original MCUboot implementation can handle that, although I could be wrong.

It can, you just need to build MCUboot twice which means you will need overlays for each build. First mcuboot build will have slot0 and slot1 for your loader application (which I assume is a modified mcuboot?) then for your second mcuboot you would enable serial recovery mode and have a single slot, slot0 (which is NOT the same slot from the first stage mcuboot)

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 15, 2024

Exactly, so practically two MCUboot apps are needed, the second one (secure loader) modified the way so it could load images from USB stick exFAT partition, rather than via serial y-modem, and install them using in-place decryption.
I think it is possible (although probably not practical) to modify 'the first' MCUboot the way so it could handle both update strategies so that 'secure loader' only needs to download the image(s) and reset the board. In any case no serial recovery will be used.

@nordicjm
Copy link
Collaborator

Exactly, so practically two MCUboot apps are needed, the second one (secure loader) modified the way so it could load images from USB stick exFAT partition, rather than via serial y-modem, and install them using in-place decryption. I think it is possible (although probably not practical) to modify 'the first' MCUboot the way so it could handle both update strategies and 'secure loader' only needs to download the image(s) and reset the board. In any case no serial recovery will be used.

You need to be aware that there's a reason secure bootloaders are incredibly simple and don't include things like file systems. File systems are complex and it makes it very easy to have bugs in them, bugs which could compromise the whole security of the device.

@tdjastrzebski
Copy link

tdjastrzebski commented Apr 15, 2024

USB MSC class complexity hence potential bugs and required fixes/improvements are the very reasons why my 'secure loader' needs two slots and ability to rollback. It cannot, or at least should not be implemented just like an enhanced external serial recovery loader.
However, it can be accomplished. I have not heard of anyone who recent years bricked his/her laptop or internet TV as a result of firmware update.
It would be nice to have the required functionality separated, that is, all the image decryption/installation handled by just one MCUboot instance able to use two (single/double slot) strategies and 'secure loader' only handling image download which is complex enough to deserve a separate app implementation, but unfortunately currently such scenario does not seem to be achievable using current MCUboot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants