Skip to content

boot_verify_dependencies() cancels every image's pending upgrade because one unrelated image's slot was never provisioned #2812

Description

@josesimoes

Title: boot_verify_dependencies() cancels every image's pending upgrade because one unrelated image's slot was never provisioned


Context

We're adding MCUboot support to .NET nanoFramework as the
bootloader for our managed-code runtime (nanoCLR), using a 2-image config (MCUBOOT_IMAGE_NUMBER = 2, which auto-enables MCUBOOT_DEPENDENCY_CHECK). Image 0 is the nanoCLR firmware; image 1 is
a "deployment" area that isn't independently executable - it's data the running CLR reads at its
own initiative.

On a fresh device, image 1's slots are simply erased flash (nothing has ever been deployed
there). While validating an upgrade of image 0 - with a perfectly valid, correctly pending image
in image 0's secondary slot - the upgrade never happened. boot_swap_type_multi() correctly
computed Swap type: test for image 0 on the initial pass, but by the time the update-dispatch
step ran, it had been silently overridden back to none.

Root cause

boot_verify_dependencies() runs once per boot whenever any image has an upgrade pending, and
walks every configured image's active slot (secondary if that image is upgrading, otherwise
its primary) looking for IMAGE_TLV_DEPENDENCY entries:

IMAGES_ITER(BOOT_CURR_IMG(state)) {
    ...
    rc = boot_verify_slot_dependencies(state, slot);
    if (rc == 0) {
        BOOT_CURR_IMG(state)++;
    } else {
        for (int idx = 0; idx < BOOT_IMAGE_NUMBER; idx++) {
            BOOT_CURR_IMG(state) = idx;
            BOOT_SWAP_TYPE(state) = BOOT_SWAP_TYPE_NONE;   // <- every image, not just this one
        }
        break;
    }
}

Image 1 has no pending upgrade, so it's checked via its primary slot - which, on a fresh
device, is erased flash. bootutil_tlv_iter_begin() correctly rejects that header (a stray
ih_protect_tlv_size byte reads back nonzero, and the following read doesn't find either TLV
info magic at the computed offset), and that rc != 0 propagates up through
boot_verify_slot_dependencies() as "dependencies not satisfied" - which isn't what actually
happened; there was no image there to have dependencies at all.

boot_verify_dependencies() can't tell the two cases apart, and cancels the pending upgrade of
every image, including image 0, which had nothing to do with image 1's empty slot.

Evidence

[INF] boot_verify_slot_dependencies: image 0 slot 1 fa_id 2
[INF] boot_verify_slot_dependencies: image 0 slot 1 returning rc=0
[INF] boot_verify_slot_dependencies: image 1 slot 0 fa_id 3
[INF] boot_verify_slot_dependencies: tlv_iter_begin failed rc=-1
[INF] boot_verify_slot_dependencies: image 1 slot 0 returning rc=-1
[INF] boot_verify_dependencies: image 1 slot 0 rc=-1, forcing swap type NONE for all images

After the fix, image 1's empty slot correctly resolves as "nothing to satisfy", and image 0's
upgrade proceeds:

[INF] boot_verify_slot_dependencies: image 1 slot 0 fa_id 3
[INF] boot_verify_slot_dependencies: no valid image at slot 0, trivially satisfied
[INF] boot_verify_slot_dependencies: image 1 slot 0 returning rc=0
[INF] Image 0 upgrade secondary slot -> primary slot

Proposed fix

A slot with no valid image has no TLV area to read a dependency from, so it can't have violated
one. Check the image magic before attempting to read the TLV area in
boot_verify_slot_dependencies(), and treat an absent image the same as an image with zero
IMAGE_TLV_DEPENDENCY entries - trivially satisfied:

/* A slot with no valid image has no dependencies to satisfy or violate. */
if (boot_img_hdr(state, slot)->ih_magic != IMAGE_MAGIC) {
    rc = 0;
    goto done;
}

We have this fix on a branch against upstream/main, tested on hardware.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions