Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions examples/htool_dfu.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,34 @@ int htool_dfu_update(const struct htool_invocation* inv) {
goto cleanup2;
}

// [`rom_ext_verify`
// call](https://github.com/lowRISC/opentitan/blob/acb2179c7f4b59d4ee2118fb24cfb4f97d920dc9/sw/device/silicon_creator/rom_ext/rom_ext.c#L398-L400)
// will check the application firmware security version against boot policy
// ([ref1](https://github.com/lowRISC/opentitan/blob/acb2179c7f4b59d4ee2118fb24cfb4f97d920dc9/sw/device/silicon_creator/rom_ext/rom_ext_verify.c#L28-L29),
// [ref2](https://github.com/lowRISC/opentitan/blob/7791207ad2a60ca8349fa1a801f3a5c639be21db/sw/device/silicon_creator/rom_ext/rom_ext_boot_policy.c#L73-L75)).
// On failure, it will return error and cause ROM_EXT to [try the other boot
// slot](https://github.com/lowRISC/opentitan/blob/acb2179c7f4b59d4ee2118fb24cfb4f97d920dc9/sw/device/silicon_creator/rom_ext/rom_ext.c#L393-L405)
// for the application firmware. To catch this case earlier, when the desired
// application firmware has security version less than the minimum BL0
// security version return an error indicating that the firmware update cannot
// be done.
// TODO: Add a flag to convey that the user wants to go ahead and try the
// update regardless of minimum BL0 security version (like maybe for testing
// that it actually works).
if (desired_app.security_version < resp.bl0_min_sec_ver) {
fprintf(
stderr,
"Desired application firmware security version %u is less than "
"currently enforced minimum application firmware security version %u\n",
desired_app.security_version, resp.bl0_min_sec_ver);
retval = -1;
goto cleanup2;
}
// Update the desired ROM_EXT version and app version based on what is
// currently running
const bool expect_desired_rom_ext_after_update =
expect_active_rom_ext_change_with_update(&desired_rom_ext, &resp);

int update_cnt =
force ? 2 : dfu_update_count(&desired_rom_ext, &desired_app, &resp);

Expand All @@ -173,15 +201,17 @@ int htool_dfu_update(const struct htool_invocation* inv) {
goto cleanup2;
}

if (!libhoth_ot_boot_slot_eq(&resp, &desired_rom_ext, &desired_app)) {
if (!libhoth_ot_boot_slot_eq(&resp, &desired_rom_ext, &desired_app,
expect_desired_rom_ext_after_update)) {
fprintf(stderr, "Boot slot is wrong after dfu update %d\n", i);
libhoth_print_dfu_error(dev, &resp, LIBHOTH_OK);
retval = -1;
goto cleanup2;
}
}

if (!libhoth_update_complete(&resp, &desired_rom_ext, &desired_app)) {
if (!libhoth_update_complete(&resp, &desired_rom_ext, &desired_app,
expect_desired_rom_ext_after_update)) {
fprintf(stderr,
"DFU update failed, running image does not match expected after %d "
"dfu updates\n",
Expand Down
5 changes: 4 additions & 1 deletion protocol/dfu_check.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ int libhoth_dfu_check(struct libhoth_device* const dev, const uint8_t* image,
// parsing purpose
libhoth_print_boot_log(resp, &desired_rom_ext, &desired_app);

if (!libhoth_update_complete(resp, &desired_rom_ext, &desired_app)) {
const bool expect_desired_rom_ext =
expect_active_rom_ext_change_with_update(&desired_rom_ext, resp);
if (!libhoth_update_complete(resp, &desired_rom_ext, &desired_app,
expect_desired_rom_ext)) {
fprintf(
stderr,
"Error: Mismatch detected between the current and desired versions.\n");
Expand Down
77 changes: 72 additions & 5 deletions protocol/opentitan_version.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,14 @@ const struct opentitan_image_version* libhoth_ot_staged_romext(
bool libhoth_ot_boot_slot_eq(
const struct opentitan_get_version_resp* resp,
const struct opentitan_image_version* desired_romext,
const struct opentitan_image_version* desired_app) {
return libhoth_ot_version_eq(libhoth_ot_boot_romext(resp), desired_romext) &&
libhoth_ot_version_eq(libhoth_ot_boot_app(resp), desired_app);
const struct opentitan_image_version* desired_app,
const bool expect_desired_rom_ext) {
bool result = libhoth_ot_version_eq(libhoth_ot_boot_app(resp), desired_app);
if (expect_desired_rom_ext) {
result &=
libhoth_ot_version_eq(libhoth_ot_boot_romext(resp), desired_romext);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are you sure we want to complete omit check the ROM_EXT version in this case? Would it make more sense to instead check that the ROM_EXT version hasn't changed?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How could it change? the version that booted previously is pinned in flash and the ROM will refuse to boot the old one we just flashed...

Copy link
Copy Markdown
Collaborator Author

@xorptr xorptr May 19, 2026

Choose a reason for hiding this comment

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

Would it make more sense to instead check that the ROM_EXT version hasn't changed

This function is used both at end of DFU update procedure, and during post-update procedure to check whether the running firmware matches what is expected (from the file). I can check that ROM_EXT version hasn't changes at end of the update procedure, but not for the other scenario (since I cannot query the version before the update in that case)

}
return result;
}

bool libhoth_ot_staged_slot_eq(
Expand All @@ -231,7 +236,69 @@ bool libhoth_ot_staged_slot_eq(
bool libhoth_update_complete(
const struct opentitan_get_version_resp* resp,
const struct opentitan_image_version* desired_romext,
const struct opentitan_image_version* desired_app) {
return libhoth_ot_boot_slot_eq(resp, desired_romext, desired_app) &&
const struct opentitan_image_version* desired_app,
const bool expect_desired_rom_ext) {
// In case of ROM_EXT update not occurring due to valid reasons (see
// `expect_active_rom_ext_change_with_update`), the staging side will still be
// running the ROM_EXT from the update package
return libhoth_ot_boot_slot_eq(resp, desired_romext, desired_app,
expect_desired_rom_ext) &&
libhoth_ot_staged_slot_eq(resp, desired_romext, desired_app);
}

bool expect_active_rom_ext_change_with_update(
const struct opentitan_image_version* desired_rom_ext_version,
const struct opentitan_get_version_resp* current_versions_resp) {
if ((desired_rom_ext_version == NULL) || (current_versions_resp == NULL)) {
return true;
}
const struct opentitan_image_version* current_rom_ext_version =
libhoth_ot_boot_romext(current_versions_resp);
// For booting ROM_EXT, ROM will give priority to slot with greater security
// version first, and then greater major version, and then greater minor
// version
// ([ref](https://github.com/lowRISC/opentitan/blob/acb2179c7f4b59d4ee2118fb24cfb4f97d920dc9/sw/device/silicon_creator/rom/boot_policy.c#L18-L41))
if (current_rom_ext_version->security_version >
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This might be easier to understand with a int opentitan_version_cmp(const opentitan_image_version* a, const opentitan_image_version* b) function, which can be easily unit tested.

Copy link
Copy Markdown
Collaborator

@korran korran May 19, 2026

Choose a reason for hiding this comment

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

I guess that would be libhoth_ot_version_cmp(), which libhoth_ot_version_eq() can delegate to...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

interestingly, libhoth_ot_version_eq() doesn't currently look at security version...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think the security version check here matters for the application firmware. The boot policy check for application firmware seems to only consult primary_bl0_slot (called here). And then rom_ext_verify call just checks against min_security_version_bl0 here (called from here).

Same concerns apply for application major and minor firmware. My understanding is that which application firmware version to boot depends primarily on primary_bl0_slot, so using this logic in checking application firmware version would be incorrect.

That's why I limited the logic to ROM_EXT only (expect_active_rom_ext_change_with_update). Even with ROM_EXT, this only helps with comparison against the active ROM_EXT (currently running). The staging ROM_EXT can have a different major and minor version. For eg:

  • Before update: Active ROM_EXT is 0.111 with security version 2. Staging ROM_EXT is 0.111 with security version 2
  • Update to image with ROM_EXT 0.110 with security version 0
  • After update: Active ROM_EXT is 0.111 with security version 2. Staging ROM_EXT is 0.110 with security version 0

I am not sure if putting this logic in a generic opentitan_version_cmp function would be helpful.

desired_rom_ext_version->security_version) {
fprintf(stderr,
"INFO: Running ROM_EXT security version %u, Desired ROM_EXT "
"security version %u\n",
current_rom_ext_version->security_version,
desired_rom_ext_version->security_version);
return false;
} else if ((current_rom_ext_version->security_version ==
desired_rom_ext_version->security_version) &&
(current_rom_ext_version->major >
desired_rom_ext_version->major)) {
fprintf(stderr,
"INFO: Running ROM_EXT has major version %u, Desired ROM_EXT major "
"version %u\n",
current_rom_ext_version->major, desired_rom_ext_version->major);
return false;
} else if ((current_rom_ext_version->security_version ==
desired_rom_ext_version->security_version) &&
(current_rom_ext_version->major ==
desired_rom_ext_version->major) &&
(current_rom_ext_version->minor >
desired_rom_ext_version->minor)) {
fprintf(stderr,
"INFO: Running ROM_EXT has minor version %u, Desired ROM_EXT minor "
"version %u\n",
current_rom_ext_version->minor, desired_rom_ext_version->minor);
return false;
}

// There is no need to check minimum ROM_EXT security version. ROM checks the
// security version of currently running ROM_EXT
// [here](https://github.com/lowRISC/opentitan/blob/acb2179c7f4b59d4ee2118fb24cfb4f97d920dc9/sw/device/silicon_creator/rom/boot_policy.c#L55-L60).
// Failure during this check will cause ROM to try the other ROM_EXT with the
// same check applied to it
// [here](https://github.com/lowRISC/opentitan/blob/acb2179c7f4b59d4ee2118fb24cfb4f97d920dc9/sw/device/silicon_creator/rom/rom.c#L761-L785).
// So it is expected that the running ROM_EXT has security version greater
// than or equal to the minimum ROM_EXT security version. In such a case, the
// conditions above will already handle firmware update to image with ROM_EXT
// version less than the minimum ROM_EXT security version (in the ROM_EXT
// security version check between desired and currently running ROM_EXT)

return true;
}
10 changes: 8 additions & 2 deletions protocol/opentitan_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,21 @@ const struct opentitan_image_version* libhoth_ot_staged_romext(
bool libhoth_ot_boot_slot_eq(
const struct opentitan_get_version_resp* resp,
const struct opentitan_image_version* desired_romext,
const struct opentitan_image_version* desired_app);
const struct opentitan_image_version* desired_app,
const bool expect_desired_rom_ext);
bool libhoth_ot_staged_slot_eq(
const struct opentitan_get_version_resp* resp,
const struct opentitan_image_version* desired_romext,
const struct opentitan_image_version* desired_app);
bool libhoth_update_complete(
const struct opentitan_get_version_resp* resp,
const struct opentitan_image_version* desired_romext,
const struct opentitan_image_version* desired_app);
const struct opentitan_image_version* desired_app,
const bool expect_desired_rom_ext);

bool expect_active_rom_ext_change_with_update(
const struct opentitan_image_version* desired_rom_ext_version,
const struct opentitan_get_version_resp* current_versions_resp);

#ifdef __cplusplus
}
Expand Down
148 changes: 139 additions & 9 deletions protocol/opentitan_version_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -142,43 +142,173 @@ TEST_F(LibHothTest, opentitan_image_compare_test) {
resp.app.booted_slot = kOpentitanBootSlotB;

EXPECT_TRUE(libhoth_ot_boot_slot_eq(&resp, libhoth_ot_boot_romext(&resp),
libhoth_ot_boot_app(&resp)));
libhoth_ot_boot_app(&resp),
/*expect_desired_rom_ext*/ true));
EXPECT_TRUE(libhoth_ot_boot_slot_eq(&resp, libhoth_ot_boot_romext(&resp),
libhoth_ot_boot_app(&resp),
/*expect_desired_rom_ext*/ false));
EXPECT_FALSE(libhoth_ot_boot_slot_eq(&resp, libhoth_ot_staged_romext(&resp),
libhoth_ot_boot_app(&resp)));
libhoth_ot_boot_app(&resp),
/*expect_desired_rom_ext*/ true));
EXPECT_TRUE(libhoth_ot_boot_slot_eq(&resp, libhoth_ot_staged_romext(&resp),
libhoth_ot_boot_app(&resp),
/*expect_desired_rom_ext*/ false));
EXPECT_FALSE(libhoth_ot_boot_slot_eq(&resp, libhoth_ot_boot_romext(&resp),
libhoth_ot_staged_app(&resp),
/*expect_desired_rom_ext*/ true));
EXPECT_FALSE(libhoth_ot_boot_slot_eq(&resp, libhoth_ot_boot_romext(&resp),
libhoth_ot_staged_app(&resp)));
libhoth_ot_staged_app(&resp),
/*expect_desired_rom_ext*/ true));

EXPECT_TRUE(libhoth_ot_staged_slot_eq(&resp, libhoth_ot_staged_romext(&resp),
libhoth_ot_staged_app(&resp)));
EXPECT_FALSE(libhoth_ot_staged_slot_eq(&resp, libhoth_ot_boot_romext(&resp),
libhoth_ot_staged_app(&resp)));
EXPECT_FALSE(libhoth_ot_staged_slot_eq(&resp, libhoth_ot_staged_romext(&resp),
libhoth_ot_boot_app(&resp)));
}

TEST_F(LibHothTest, TestLibhothUpdateComplete) {
struct opentitan_image_version romext = {
.major = 6,
.minor = 111,
.security_version = 7,
.timestamp = 1234,
.measurement = {},
};
struct opentitan_image_version romext_security_version_larger = {
.major = 6,
.minor = 112,
.security_version = 9,
.timestamp = 1234,
.measurement = {},
};
struct opentitan_image_version romext_major_version_larger = {
.major = 8,
.minor = 111,
.security_version = 7,
.timestamp = 1234,
.measurement = {},
};
struct opentitan_image_version romext_minor_version_larger = {
.major = 6,
.minor = 114,
.security_version = 7,
.timestamp = 1234,
.measurement = {},
};
struct opentitan_image_version romext_minor_version_smaller = {
.major = 6,
.minor = 110,
.security_version = 7,
.timestamp = 1234,
.measurement = {},
};

struct opentitan_image_version app = {
struct opentitan_image_version app_1 = {
.major = 1,
.minor = 116,
.security_version = 2,
.timestamp = 6789,
.measurement = {},
};
struct opentitan_image_version app_2 = {
.major = 1,
.minor = 115,
.security_version = 2,
.timestamp = 6789,
.measurement = {},
};

struct opentitan_get_version_resp resp = {};

// Case 1: Successful update of both halves
resp.rom_ext.booted_slot = kOpentitanBootSlotA;
resp.app.booted_slot = kOpentitanBootSlotA;
resp.rom_ext.slots[0] = romext;
resp.rom_ext.slots[1] = romext;
resp.app.slots[0] = app;
resp.app.slots[1] = app;
resp.app.slots[0] = app_1;
resp.app.slots[1] = app_1;

EXPECT_TRUE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_TRUE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));

EXPECT_TRUE(libhoth_update_complete(&resp, &romext, &app));
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &romext));
EXPECT_FALSE(libhoth_update_complete(&resp, &app, &app));
// Case 2: Active ROM_EXT did not change since running ROM_EXT had greater
// security version
resp.rom_ext.booted_slot = kOpentitanBootSlotA;
resp.app.booted_slot = kOpentitanBootSlotA;
resp.rom_ext.slots[0] = romext_security_version_larger;
resp.rom_ext.slots[1] = romext;
resp.app.slots[0] = app_1;
resp.app.slots[1] = app_1;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_TRUE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));

// Case 3: Active ROM_EXT did not change since running ROM_EXT had greater
// major version
resp.rom_ext.booted_slot = kOpentitanBootSlotA;
resp.app.booted_slot = kOpentitanBootSlotA;
resp.rom_ext.slots[0] = romext_major_version_larger;
resp.rom_ext.slots[1] = romext;
resp.app.slots[0] = app_1;
resp.app.slots[1] = app_1;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_TRUE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));

// Case 4: Active ROM_EXT did not change since running ROM_EXT had greater
// minor version
resp.rom_ext.booted_slot = kOpentitanBootSlotA;
resp.app.booted_slot = kOpentitanBootSlotA;
resp.rom_ext.slots[0] = romext_minor_version_larger;
resp.rom_ext.slots[1] = romext;
resp.app.slots[0] = app_1;
resp.app.slots[1] = app_1;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_TRUE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));

// Case 5: ROM_EXT did not change due to some reason
resp.rom_ext.booted_slot = kOpentitanBootSlotA;
resp.app.booted_slot = kOpentitanBootSlotA;
resp.rom_ext.slots[0] = romext_minor_version_smaller;
resp.rom_ext.slots[1] = romext_minor_version_smaller;
resp.app.slots[0] = app_1;
resp.app.slots[1] = app_1;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));

// Case 6: Application firmware did not update due to some reason
resp.rom_ext.booted_slot = kOpentitanBootSlotA;
resp.app.booted_slot = kOpentitanBootSlotA;
resp.rom_ext.slots[0] = romext;
resp.rom_ext.slots[1] = romext;
resp.app.slots[0] = app_2;
resp.app.slots[1] = app_2;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));
resp.app.slots[0] = app_1;
resp.app.slots[1] = app_2;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));
resp.app.slots[0] = app_2;
resp.app.slots[1] = app_1;
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/true));
EXPECT_FALSE(libhoth_update_complete(&resp, &romext, &app_1,
/*expect_desired_rom_ext=*/false));
}

TEST_F(LibHothTest, ExtractOtBundleBoundsCheckLargeOffset) {
Expand Down
Loading