Allow firmware updates for images that don't use a firmware header

Also remove binding to specific error codes when validating the recovery image.  Expecting specific error codes is a fragile approach and didn't offer much benefit.
This commit is contained in:
Christopher Weimer 2022-10-12 19:31:59 +00:00
Родитель 8c4800bb00
Коммит ed5f1c245c
4 изменённых файлов: 1471 добавлений и 775 удалений

Просмотреть файл

@ -27,12 +27,7 @@ struct firmware_image {
* @param flash The flash device that contains the firmware image.
* @param base_addr The starting address of the new firmware image.
*
* @return 0 if the image reference was updated successfully or an error code. Load-time
* validation errors will generate one of the following errors:
* - FIRMWARE_IMAGE_INVALID_FORMAT
* - FIRMWARE_IMAGE_BAD_CHECKSUM
* - KEY_MANIFEST_INVALID_FORMAT
* - FIRMWARE_HEADER or IMAGE_HEADER validation errors
* @return 0 if the image reference was updated successfully or an error code.
*/
int (*load) (struct firmware_image *fw, struct flash *flash, uint32_t base_addr);

Просмотреть файл

@ -94,6 +94,26 @@ void firmware_update_set_image_offset (struct firmware_update *updater, int offs
}
}
/**
* Indicate to the updater if the firmware image is required to contain a firmware header or not.
* When a firmware header is required, images that fail to provide one will result in failure to
* apply that image.
*
* It is expected that this would be configured during initialization based on the firmware image
* expectations for a particular system.
*
* By default, the updater requires a firmware header on the image.
*
* @param updater The firmware updater to configure.
* @param has_fw_header Flag indicating if a firmware header is required for the update to succeed.
*/
void firmware_update_require_firmware_header (struct firmware_update *updater, bool has_fw_header)
{
if (updater != NULL) {
updater->no_fw_header = !has_fw_header;
}
}
/**
* Indicate to the firmware updater if the recovery image on flash is currently good.
*
@ -134,17 +154,13 @@ void firmware_update_set_recovery_revision (struct firmware_update *updater, int
* only if it knows a good recovery image exists.
*
* @param updater The updater to configure.
*
* @return 0 if the operation completed successfully or an error code. A successful return only
* means that the updater was able to determine if the recovery was good or not. It doesn't
* indicate the validity of the image.
*/
int firmware_update_validate_recovery_image (struct firmware_update *updater)
void firmware_update_validate_recovery_image (struct firmware_update *updater)
{
int status = 0;
int status;
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
return;
}
if (updater->flash->recovery_flash) {
@ -166,33 +182,18 @@ int firmware_update_validate_recovery_image (struct firmware_update *updater)
if (header != NULL) {
status = firmware_header_get_recovery_revision (header, &updater->recovery_rev);
}
else {
else if (!updater->no_fw_header) {
status = FIRMWARE_UPDATE_NO_FIRMWARE_HEADER;
}
}
}
}
updater->recovery_bad = (status != 0);
debug_log_create_entry (
(updater->recovery_bad) ? DEBUG_LOG_SEVERITY_WARNING : DEBUG_LOG_SEVERITY_INFO,
DEBUG_LOG_COMPONENT_CERBERUS_FW, FIRMWARE_LOGGING_RECOVERY_IMAGE, updater->recovery_bad,
status);
/* TODO: What about ECC signature errors or revocation checks fails? This seems generally
* fragile. Maybe we should get rid of it, along with the list of validation codes documented
* on firmware_image.load. That seems like it could become incomplete.
*
* A better approach is probably to just swallow all errors (maybe change this to return void?).
* We already log it and mark the recovery as bad in all error cases. It's not clear what
* benefit there is from the distinction of errors here. */
if ((status == FIRMWARE_IMAGE_INVALID_FORMAT) || (status == FIRMWARE_IMAGE_BAD_CHECKSUM) ||
(status == KEY_MANIFEST_INVALID_FORMAT) || (status == FIRMWARE_IMAGE_BAD_SIGNATURE) ||
(status == FIRMWARE_HEADER_BAD_FORMAT_LENGTH) ||
((status >= IMAGE_HEADER_NOT_MINIMUM_SIZE) && (status <= IMAGE_HEADER_TOO_LONG))) {
status = 0;
updater->recovery_bad = (status != 0);
debug_log_create_entry (
(updater->recovery_bad) ? DEBUG_LOG_SEVERITY_WARNING : DEBUG_LOG_SEVERITY_INFO,
DEBUG_LOG_COMPONENT_CERBERUS_FW, FIRMWARE_LOGGING_RECOVERY_IMAGE, updater->recovery_bad,
status);
}
return ROT_IS_ERROR (status) ? status : 0;
}
/**
@ -544,9 +545,9 @@ static int firmware_update_write_image (struct firmware_update *updater,
* - Validate the data store in the staging flash region to ensure a good image.
* - Save the application state that should be restored after the update.
* - Copy the image in staging flash to active flash.
* - Copy the image in staging flash to recovery flash, if the recovery certificate has been
* - Copy the image in staging flash to recovery flash, if the recovery manifest has been
* revoked.
* - Update certificate revocation information in the device.
* - Update manifest revocation information in the device.
*
* @param updater The updater that should run.
* @param callback A set of notification handlers to use during the update process. This can be
@ -563,7 +564,7 @@ int firmware_update_run_update (struct firmware_update *updater,
struct firmware_header *header = NULL;
bool recovery_updated = false;
bool img_good;
int cert_revoked;
int manifest_revoked;
int new_revision;
int allow_update;
int status;
@ -601,20 +602,27 @@ int firmware_update_run_update (struct firmware_update *updater,
}
header = updater->fw->get_firmware_header (updater->fw);
if (header == NULL) {
if (header != NULL) {
status = firmware_header_get_recovery_revision (header, &new_revision);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return status;
}
if (new_revision < updater->min_rev) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return FIRMWARE_UPDATE_REJECTED_ROLLBACK;
}
}
else if (!updater->no_fw_header) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return FIRMWARE_UPDATE_NO_FIRMWARE_HEADER;
}
status = firmware_header_get_recovery_revision (header, &new_revision);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return status;
}
if (new_revision < updater->min_rev) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return FIRMWARE_UPDATE_REJECTED_ROLLBACK;
else {
/* There is no FW header on the image, so just apply the updater's recovery revision to the
* new image. Without a FW header, the recovery image will only get updated during
* manifest revocation flows. */
new_revision = updater->recovery_rev;
}
new_len = updater->fw->get_image_size (updater->fw);
@ -667,7 +675,7 @@ int firmware_update_run_update (struct firmware_update *updater,
return status;
}
/* Check for certificate revocation. */
/* Check for manifest revocation. */
firmware_update_status_change (callback, UPDATE_STATUS_CHECK_REVOCATION);
status = updater->fw->load (updater->fw, updater->flash->active_flash,
updater->flash->active_addr + updater->img_offset);
@ -682,15 +690,15 @@ int firmware_update_run_update (struct firmware_update *updater,
return FIRMWARE_UPDATE_NO_KEY_MANIFEST;
}
cert_revoked = manifest->revokes_old_manifest (manifest);
if (ROT_IS_ERROR (cert_revoked)) {
manifest_revoked = manifest->revokes_old_manifest (manifest);
if (ROT_IS_ERROR (manifest_revoked)) {
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_CHK_FAIL);
return cert_revoked;
return manifest_revoked;
}
/* Check if recovery update is necessary. */
firmware_update_status_change (callback, UPDATE_STATUS_CHECK_RECOVERY);
if (cert_revoked || (updater->recovery_rev != new_revision)) {
if (manifest_revoked || (updater->recovery_rev != new_revision)) {
if (updater->flash->recovery_flash && !recovery_updated) {
struct flash *backup;
uint32_t backup_addr;
@ -722,8 +730,8 @@ int firmware_update_run_update (struct firmware_update *updater,
updater->recovery_rev = new_revision;
if (cert_revoked) {
/* Revoke the old certificate. */
if (manifest_revoked) {
/* Revoke the old manifest. */
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_CERT);
status = manifest->update_revocation (manifest);
if (status != 0) {

Просмотреть файл

@ -101,13 +101,14 @@ struct firmware_update_hooks {
* Run additional verification on a boot image stored on flash. This will be called after
* running typical verification on a firmware image.
*
* This verification will not be called for the image in staging flash.
* This verification will not be called for an image in staging flash.
*
* @param updater The firmware updater to run the verification.
* @param flash The flash device that contains the boot image to verify.
* @param address The base address of the boot image.
*
* @return 0 if the image is valid, 1 if it is not, or an error code.
* @return 0 if the image is valid or an error code. If the boot image is not valid,
* FIRMWARE_UPDATE_INVALID_BOOT_IMAGE will be returned.
*/
int (*verify_boot_image) (struct firmware_update *updater, struct flash *flash,
uint32_t address);
@ -123,6 +124,7 @@ struct firmware_update {
struct hash_engine *hash; /**< The hash engine to use during update .*/
struct app_context *context; /**< The platform application context API. */
struct flash_updater update_mgr; /**< Update manager for writing data to flash. */
bool no_fw_header; /**< Indication that a firmware header is not required. */
bool recovery_bad; /**< Indication if the recovery image on flash is bad. */
int recovery_rev; /**< Revision ID of the current recovery image. */
int min_rev; /**< Minimum revision ID allowed for update. */
@ -151,13 +153,15 @@ int firmware_update_init (struct firmware_update *updater, const struct firmware
void firmware_update_release (struct firmware_update *updater);
void firmware_update_set_image_offset (struct firmware_update *updater, int offset);
void firmware_update_require_firmware_header (struct firmware_update *updater, bool has_fw_header);
void firmware_update_set_recovery_good (struct firmware_update *updater, bool img_good);
void firmware_update_set_recovery_revision (struct firmware_update *updater, int revision);
int firmware_update_validate_recovery_image (struct firmware_update *updater);
void firmware_update_set_recovery_good (struct firmware_update *updater, bool img_good);
void firmware_update_validate_recovery_image (struct firmware_update *updater);
int firmware_update_is_recovery_good (struct firmware_update *updater);
int firmware_update_restore_recovery_image (struct firmware_update *updater);
int firmware_update_restore_active_image (struct firmware_update *updater);
int firmware_update_is_recovery_good (struct firmware_update *updater);
int firmware_update_add_observer (struct firmware_update *updater,
struct firmware_update_observer *observer);
@ -198,6 +202,7 @@ enum {
FIRMWARE_UPDATE_REJECTED_ROLLBACK = FIRMWARE_UPDATE_ERROR (0x0e), /**< The new image revision ID is a disallowed version. */
FIRMWARE_UPDATE_NO_RECOVERY_IMAGE = FIRMWARE_UPDATE_ERROR (0x0f), /**< There is no recovery image available for the operation. */
FIRMWARE_UPDATE_RESTORE_NOT_NEEDED = FIRMWARE_UPDATE_ERROR (0x10), /**< An image restore operation was not necessary. */
FIRMWARE_UPDATE_INVALID_BOOT_IMAGE = FIRMWARE_UPDATE_ERROR (0x11), /**< The boot image is not valid based on additional verification. */
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу