core/firmware/firmware_update.c (665 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "firmware_logging.h"
#include "firmware_update.h"
#include "common/unused.h"
#include "flash/flash_common.h"
#include "flash/flash_util.h"
/**
* Initialize the platform firmware updater.
*
* @param updater The updater to initialize.
* @param state Variable context for the updater. This must be uninitialized.
* @param flash The device and address mapping for firmware images.
* @param context The application context API.
* @param fw The platform handler for firmware images.
* @param security The manager for the device security policy.
* @param hash The hash engine to use during updates.
* @param allowed_revision The lowest image ID that will be allowed for firmware updates.
*
* @return 0 if the updater was successfully initialized or an error code.
*/
int firmware_update_init (struct firmware_update *updater, struct firmware_update_state *state,
const struct firmware_flash_map *flash, const struct app_context *context,
const struct firmware_image *fw, const struct security_manager *security,
const struct hash_engine *hash, int allowed_revision)
{
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
memset (updater, 0, sizeof (struct firmware_update));
updater->state = state;
updater->flash = flash;
updater->fw = fw;
updater->security = security;
updater->context = context;
updater->hash = hash;
return firmware_update_init_state (updater, allowed_revision);
}
/**
* Initialize the platform firmware updater.
*
* Firmware images processed by the updater are not required to contain a firmware header. If the
* firmware header is present, it will be processed. If the firmware header is not present, the
* update will proceed without it and any workflows that require information from the header will
* be skipped.
*
* @param updater The updater to initialize.
* @param state Variable context for the updater. This must be uninitialized.
* @param flash The device and address mapping for firmware images.
* @param context The application context API.
* @param fw The platform handler for firmware images.
* @param security The manager for the device security policy.
* @param hash The hash engine to use during updates.
* @param allowed_revision The lowest image ID that will be allowed for firmware updates.
*
* @return 0 if the updater was successfully initialized or an error code.
*/
int firmware_update_init_no_firmware_header (struct firmware_update *updater,
struct firmware_update_state *state, const struct firmware_flash_map *flash,
const struct app_context *context, const struct firmware_image *fw,
const struct security_manager *security, const struct hash_engine *hash, int allowed_revision)
{
int status;
status = firmware_update_init (updater, state, flash, context, fw, security, hash,
allowed_revision);
if (status == 0) {
updater->no_fw_header = true;
}
return status;
}
/**
* Initialize only the variable state for the platform firmware updater. The rest of the firmware
* update instance is assumed to have already been initialized.
*
* This would generally be used with a statically initialized instance.
*
* @param updater The updater instance that contains the state to initialize.
* @param allowed_revision The lowest image ID that will be allowed for firmware updates.
*
* @return 0 if the state was successfully initialized or an error code.
*/
int firmware_update_init_state (const struct firmware_update *updater, int allowed_revision)
{
int status;
if ((updater == NULL) || (updater->state == NULL) || (updater->flash == NULL) ||
(updater->context == NULL) || (updater->fw == NULL) || (updater->security == NULL) ||
(updater->hash == NULL)) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
if ((updater->flash->active_flash == NULL) || (updater->flash->staging_flash == NULL)) {
return FIRMWARE_UPDATE_INVALID_FLASH_MAP;
}
if ((updater->flash->backup_flash == NULL) && (updater->flash->recovery_flash == NULL)) {
return FIRMWARE_UPDATE_INVALID_FLASH_MAP;
}
memset (updater->state, 0, sizeof (struct firmware_update_state));
status = flash_updater_init (&updater->state->update_mgr, updater->flash->staging_flash,
updater->flash->staging_addr, updater->flash->staging_size);
if (status != 0) {
return status;
}
status = observable_init (&updater->state->observable);
if (status != 0) {
flash_updater_release (&updater->state->update_mgr);
return status;
}
updater->state->recovery_rev = -1;
updater->state->min_rev = allowed_revision;
return 0;
}
/**
* Release the resources used by a firmware updater.
*
* @param updater The updater to release.
*/
void firmware_update_release (const struct firmware_update *updater)
{
if (updater) {
observable_release (&updater->state->observable);
flash_updater_release (&updater->state->update_mgr);
}
}
/**
* Set the offset to apply to each firmware region when writing images.
*
* This should be called only during initialization if the updater requires an image offset.
*
* @param updater The firmware updater to configure.
* @param offset The offset to apply to images.
*/
void firmware_update_set_image_offset (const struct firmware_update *updater, int offset)
{
if (updater != NULL) {
updater->state->img_offset = offset;
flash_updater_apply_update_offset (&updater->state->update_mgr, offset);
}
}
/**
* Provide the firmware updater with the image ID of the current recovery image. This ID will be
* checked during updates to see if the recovery image also needs updating.
*
* This should only be used is specific scenarios where forcing a particular recovery revision is
* necessary. Generally, firmware_update_validate_recovery_image should be preferred to configure
* this value.
*
* @param updater The firmware updater to configure.
* @param revision The revision ID of the recovery image.
*/
void firmware_update_set_recovery_revision (const struct firmware_update *updater, int revision)
{
if (updater != NULL) {
updater->state->recovery_rev = revision;
}
}
/**
* Indicate to the firmware updater if the recovery image on flash is currently good.
*
* It is expected that this would be set once during initialization for a system that has a recovery
* image. After initialization, the state of the recovery image will be automatically tracked by
* the updater. This state will also get set by firmware_update_validate_recovery_image.
*
* This can also be used to force the updater to treat the recovery image as bad to trigger recovery
* update flows that may otherwise not get executed.
*
* @param updater The firmware updater to configure.
* @param img_good Flag indicating if the current recovery image is good.
*/
void firmware_update_set_recovery_good (const struct firmware_update *updater, bool img_good)
{
if (updater != NULL) {
updater->state->recovery_bad = !img_good;
}
}
/**
* Trigger the notification callback for a firmware update status change.
*
* @param callback The notification callback to trigger.
* @param status The status to notify.
*/
static void firmware_update_status_change (const struct firmware_update_notification *callback,
enum firmware_update_status status)
{
if ((callback != NULL) && (callback->status_change != NULL)) {
callback->status_change (callback, status);
}
}
/**
* Load an image context from flash and check if the image is valid.
*
* @param updater The updater to use for verification.
* @param callback Status callback to report status in case of failures. This can be null to not
* have status reporting.
* @param flash The flash device that contains the image to load and verify.
* @param address Base address of the image. This does not include any image offset.
* @param check_bytes Flag indicating if remaining bytes of an active update should be considered
* during verification.
* @param boot_image Flag indicating if boot image verification needs to be run.
* @param check_rollback Flag indicating if recovery revision rollback should be checked.
* @param img_size Output for the total size of the image. This is only valid if the image was
* successfully verified. This can be null if the image size is not needed.
* @param recovery_rev Output for the recovery revision from the firmware header. This is only
* valid if the image was successfully verified. If the image does not have a firmware header and
* one is not required, the value will not be updated. This can be null if the recovery revision is
* not needed.
*
* @return 0 if the image the image is valid and all required information was retrieved, or an error
* code.
*/
static int firmware_update_load_and_verify_image (const struct firmware_update *updater,
const struct firmware_update_notification *callback, const struct flash *flash,
uint32_t address, bool check_bytes, bool boot_image, bool check_rollback, size_t *img_size,
int *recovery_rev)
{
int status;
firmware_update_status_change (callback, UPDATE_STATUS_VERIFYING_IMAGE);
if (check_bytes) {
if (flash_updater_get_remaining_bytes (&updater->state->update_mgr) > 0) {
firmware_update_status_change (callback, UPDATE_STATUS_INCOMPLETE_IMAGE);
return FIRMWARE_UPDATE_INCOMPLETE_IMAGE;
}
}
status = updater->fw->load (updater->fw, flash, address + updater->state->img_offset);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_VERIFY_FAILURE);
return status;
}
status = updater->fw->verify (updater->fw, updater->hash);
if (status != 0) {
if ((status == FIRMWARE_IMAGE_BAD_SIGNATURE) ||
(status == FIRMWARE_IMAGE_MANIFEST_REVOKED)) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
}
else {
firmware_update_status_change (callback, UPDATE_STATUS_VERIFY_FAILURE);
}
return status;
}
if (boot_image && updater->internal.verify_boot_image) {
status = updater->internal.verify_boot_image (updater, flash, address);
if (status != 0) {
return status;
}
}
if (img_size) {
int img_length = updater->fw->get_image_size (updater->fw);
if (ROT_IS_ERROR (img_length)) {
firmware_update_status_change (callback, UPDATE_STATUS_VERIFY_FAILURE);
return img_length;
}
*img_size = img_length;
}
if (recovery_rev) {
const struct firmware_header *header = NULL;
const struct security_policy *policy;
int img_revision;
header = updater->fw->get_firmware_header (updater->fw);
if (header != NULL) {
status = firmware_header_get_recovery_revision (header, &img_revision);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return status;
}
if (check_rollback) {
policy = security_manager_get_security_policy (updater->security);
if (security_policy_enforce_anti_rollback (policy)) {
if (img_revision < updater->state->min_rev) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return FIRMWARE_UPDATE_REJECTED_ROLLBACK;
}
}
}
*recovery_rev = img_revision;
}
else if (!updater->no_fw_header) {
firmware_update_status_change (callback, UPDATE_STATUS_INVALID_IMAGE);
return FIRMWARE_UPDATE_NO_FIRMWARE_HEADER;
}
}
return 0;
}
/**
* Set the updater state of the recovery image by actively reading the flash contents. If the
* updater is not configured to use a recovery image, no operation is performed.
*
* If there is a valid recovery image, the revision of the recovery image will be cached for use
* during updates. There is no need to call firmware_update_set_recovery_revision.
*
* If there is an error while trying to determine the validity of the recovery image, the internal
* state will be updated as if the recovery image is bad. This will ensure that updates proceed
* only if it knows a good recovery image exists.
*
* @param updater The updater to configure.
*/
void firmware_update_validate_recovery_image (const struct firmware_update *updater)
{
int status;
if (updater == NULL) {
return;
}
if (updater->flash->recovery_flash) {
status = firmware_update_load_and_verify_image (updater, NULL,
updater->flash->recovery_flash, updater->flash->recovery_addr, false, true, false, NULL,
&updater->state->recovery_rev);
updater->state->recovery_bad = (status != 0);
debug_log_create_entry ((updater->state->recovery_bad) ? DEBUG_LOG_SEVERITY_WARNING :
DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_RECOVERY_IMAGE, updater->state->recovery_bad, status);
}
}
/**
* Indicate if the recovery image on flash is currently good.
*
* @param updater The firmware updater to query.
*
* @return 1 if the recovery image is good, 0 if the recovery image is bad, or an error code.
*/
int firmware_update_is_recovery_good (const struct firmware_update *updater)
{
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
return updater->state->recovery_bad ? 0 : 1;
}
/**
* Program a bootable region of flash with a new image.
*
* @param updater The updater to use for programming.
* @param dest The bootable flash device to program.
* @param dest_addr The address program the image to.
* @param src The flash device with the image to copy to the bootable region.
* @param src_addr The starting address of the new image.
* @param length The length of the new image.
* @param page The page size of flash being written.
*
* @return 0 if the new image was copied successfully to the active region or an error code.
*/
static int firmware_update_program_bootable (const struct firmware_update *updater,
const struct flash *dest, uint32_t dest_addr, const struct flash *src, uint32_t src_addr,
size_t length, uint32_t page)
{
int status;
UNUSED (updater);
if (length > page) {
status = flash_copy_ext_to_blank_and_verify (dest, dest_addr + page, src, src_addr + page,
length - page);
if (status == 0) {
status = flash_copy_ext_to_blank_and_verify (dest, dest_addr, src, src_addr, page);
}
}
else {
status = flash_copy_ext_to_blank_and_verify (dest, dest_addr, src, src_addr, length);
}
return status;
}
/**
* Call the internal updater function to finalize an image installation.
*
* @param updater The updater instance.
* @param flash The flash that has the new image.
* @param address The base address for the image region.
*
* @return 0 if the image was successfully finalized or an error code.
*/
static int firmware_update_finalize_image (const struct firmware_update *updater,
const struct flash *flash, uint32_t address)
{
if (updater->internal.finalize_image) {
return updater->internal.finalize_image (updater, flash, address);
}
return 0;
}
/**
* Write a new firmware image to a region in flash from the staging region.
*
* The image currently in flash will optionally be backed up. If there is an error writing the new
* image, an attempt will be made to restore the current image from the backup.
*
* @param updater The updater being executed.
* @param callback The updated notification handlers.
* @param dest The destination flash device for the new image.
* @param dest_addr The destination address for the new image.
* @param backup The backup flash device. This can be null to not create a backup.
* @param backup_addr The address to store the backup.
* @param src The source flash device that contains the new image.
* @param src_addr The source address of the new image.
* @param update_len The length of the new image.
* @param backup_start The status to report when image backup has started.
* @param backup_fail The status to report if image backup has failed.
* @param update_start The status to report when the image has started update.
* @param update_fail The status to report if the image update failed.
* @param img_good Optional output indicating of the destination region contains a good image at the
* end of this process. It does not mean the new image is in the region, just that there is a good
* one, such as when a backup image is restored in error handling.
*
* @return 0 if the new firmware image was successfully written or an error code.
*/
static int firmware_update_write_image (const struct firmware_update *updater,
const struct firmware_update_notification *callback, const struct flash *dest,
uint32_t dest_addr, const struct flash *backup, uint32_t backup_addr, const struct flash *src,
uint32_t src_addr, size_t update_len, enum firmware_update_status backup_start,
enum firmware_update_status backup_fail, enum firmware_update_status update_start,
enum firmware_update_status update_fail, bool *img_good)
{
int backup_len = 0;
uint32_t page;
int status;
if (img_good) {
*img_good = true;
}
if (backup) {
/* Backup the current image. */
firmware_update_status_change (callback, backup_start);
status = updater->fw->load (updater->fw, dest, dest_addr + updater->state->img_offset);
if (status != 0) {
firmware_update_status_change (callback, backup_fail);
return status;
}
backup_len = updater->fw->get_image_size (updater->fw);
if (ROT_IS_ERROR (backup_len)) {
firmware_update_status_change (callback, backup_fail);
return backup_len;
}
status = flash_copy_ext_and_verify (backup, backup_addr + updater->state->img_offset, dest,
dest_addr + updater->state->img_offset, backup_len);
if (status != 0) {
firmware_update_status_change (callback, backup_fail);
return status;
}
}
/* Update the new image from staging flash. */
firmware_update_status_change (callback, update_start);
status = dest->get_page_size (dest, &page);
if (status != 0) {
firmware_update_status_change (callback, update_fail);
return status;
}
if (img_good) {
*img_good = false;
}
status = flash_erase_region_and_verify (dest, dest_addr,
update_len + updater->state->img_offset);
if (status != 0) {
firmware_update_status_change (callback, update_fail);
return status;
}
status = firmware_update_program_bootable (updater, dest,
dest_addr + updater->state->img_offset, src, src_addr + updater->state->img_offset,
update_len, page);
if (status == 0) {
status = firmware_update_finalize_image (updater, dest, dest_addr);
}
if (status != 0) {
if (backup) {
/* Try to restore the image that was backed up. */
if (flash_erase_region_and_verify (dest, dest_addr,
backup_len + updater->state->img_offset) == 0) {
if (firmware_update_program_bootable (updater, dest,
dest_addr + updater->state->img_offset, backup,
backup_addr + updater->state->img_offset, backup_len, page) == 0) {
if (firmware_update_finalize_image (updater, dest, dest_addr) == 0) {
*img_good = true;
}
}
}
}
firmware_update_status_change (callback, update_fail);
return status;
}
if (img_good) {
*img_good = true;
}
return 0;
}
/**
* Restore an image from one flash region to another.
*
* @param updater The updater to use for image restoration.
* @param dest The flash device to restore to.
* @param dest_addr The address to restore the image to.
* @param src The flash device with the image to restore from.
* @param src_addr The address to restore from.
* @param recovery_rev Optional output parameter for the recovery revision for the image that was
* restored.
*
* @return 0 if image was successfully restored or an error code.
*/
static int firmware_update_restore_image (const struct firmware_update *updater,
const struct flash *dest, uint32_t dest_addr, const struct flash *src, uint32_t src_addr,
int *recovery_rev)
{
size_t img_len = 0;
int status;
status = firmware_update_load_and_verify_image (updater, NULL, src, src_addr, false, false,
false, &img_len, recovery_rev);
if (status != 0) {
return status;
}
/* Enum values for the firmware_update_status are not relevant since the callback will always be
* null for this call. */
return firmware_update_write_image (updater, NULL, dest, dest_addr, NULL, 0, src, src_addr,
img_len, UPDATE_STATUS_SUCCESS, UPDATE_STATUS_SUCCESS, UPDATE_STATUS_SUCCESS,
UPDATE_STATUS_SUCCESS, NULL);
}
/**
* Use the active image to restore a corrupt recovery image. Only if the recovery image is known to
* be bad will anything be changed.
*
* @param updater The updater to use for the image restore operation.
*
* @return 0 if the recovery image was restored successfully or an error code. If the recovery
* image is already good, FIRMWARE_UPDATE_RESTORE_NOT_NEEDED will be returned.
*/
int firmware_update_restore_recovery_image (const struct firmware_update *updater)
{
int status = FIRMWARE_UPDATE_NO_RECOVERY_IMAGE;
int recovery_rev = -1;
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
if (updater->flash->recovery_flash) {
if (updater->state->recovery_bad) {
debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_RECOVERY_RESTORE_START, 0, 0);
status = firmware_update_restore_image (updater, updater->flash->recovery_flash,
updater->flash->recovery_addr, updater->flash->active_flash,
updater->flash->active_addr, &recovery_rev);
if (status == 0) {
updater->state->recovery_bad = false;
updater->state->recovery_rev = recovery_rev;
debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_RECOVERY_IMAGE, 0, 0);
}
else {
debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_RECOVERY_RESTORE_FAIL, status, 0);
}
}
else {
status = FIRMWARE_UPDATE_RESTORE_NOT_NEEDED;
}
}
return status;
}
/**
* Use the recovery image to restore the active image. The state of the active image is not checked
* before updating it with the recovery image.
*
* @param updater The updater to use for the image restore operation.
*
* @return 0 if the active image was restored successfully or an error code. The result is returned
* in case the caller needs this information, but the result of the operation will have already been
* logged.
*/
int firmware_update_restore_active_image (const struct firmware_update *updater)
{
int status;
debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_ACTIVE_RESTORE_START, 0, 0);
if (updater == NULL) {
status = FIRMWARE_UPDATE_INVALID_ARGUMENT;
goto done;
}
if (updater->flash->recovery_flash) {
status = firmware_update_restore_image (updater, updater->flash->active_flash,
updater->flash->active_addr, updater->flash->recovery_flash,
updater->flash->recovery_addr, NULL);
}
else {
status = FIRMWARE_UPDATE_NO_RECOVERY_IMAGE;
}
done:
debug_log_create_entry ((status == 0) ? DEBUG_LOG_SEVERITY_INFO : DEBUG_LOG_SEVERITY_ERROR,
DEBUG_LOG_COMPONENT_CERBERUS_FW, FIRMWARE_LOGGING_ACTIVE_RESTORE_DONE, status, 0);
return status;
}
/**
* Determine if the contents of the recovery flash exactly match the contents of the active flash.
* This does no verification of either image and only parses enough to make the comparison. If the
* updater is not configured to use a recovery image, the call reports a match.
*
* @param updater The firmware updater to use for the comparison.
*
* @return 0 if the images exactly match, 1 if they don't, or an error code.
*/
int firmware_update_recovery_matches_active_image (const struct firmware_update *updater)
{
int active_len;
int recovery_len;
int status = 0;
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
if (updater->flash->recovery_flash) {
status = updater->fw->load (updater->fw, updater->flash->active_flash,
updater->flash->active_addr + updater->state->img_offset);
if (status != 0) {
return status;
}
active_len = updater->fw->get_image_size (updater->fw);
if (ROT_IS_ERROR (active_len)) {
return active_len;
}
status = updater->fw->load (updater->fw, updater->flash->recovery_flash,
updater->flash->recovery_addr + updater->state->img_offset);
if (status != 0) {
return status;
}
recovery_len = updater->fw->get_image_size (updater->fw);
if (ROT_IS_ERROR (recovery_len)) {
return recovery_len;
}
/* If the images are not the same length, no point in checking the flash contents. */
if (active_len != recovery_len) {
return 1;
}
status = flash_verify_copy_ext (updater->flash->active_flash,
updater->flash->active_addr + updater->state->img_offset,
updater->flash->recovery_flash,
updater->flash->recovery_addr + updater->state->img_offset, active_len);
if ((status != 0) && (status != FLASH_UTIL_DATA_MISMATCH)) {
return status;
}
}
return (status == 0) ? 0 : 1;
}
/**
* Add an observer for firmware update notifications.
*
* @param updater The firmware updater to register with.
* @param observer The observer to add.
*
* @return 0 if the observer was successfully added or an error code.
*/
int firmware_update_add_observer (const struct firmware_update *updater,
const struct firmware_update_observer *observer)
{
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
return observable_add_observer (&updater->state->observable, (void*) observer);
}
/**
* Remove an observer from firmware update notifications.
*
* @param updater The firmware updater to deregister from.
* @param observer The observer to remove.
*
* @return 0 if the observer was successfully removed or an error code.
*/
int firmware_update_remove_observer (const struct firmware_update *updater,
const struct firmware_update_observer *observer)
{
if (updater == NULL) {
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
return observable_remove_observer (&updater->state->observable, (void*) observer);
}
/**
* Update the image on recovery flash.
*
* @param updater The updater to execute.
* @param callback Status callback to report status updates. This can be null to not have status
* reporting.
* @param flash The flash device containing the image to use for recovery updates.
* @param address Base address of the image to use for recovery updates.
* @param img_length Length of the image to use for recovery updates.
* @param backup Optional flash device containing the backup region for the recovery image.
* @param back_addr Base address of the backup region of flash.
* @param img_good Optional output indicating of the destination region contains a good image at the
* end of this process. It does not mean the new image is in the region, just that there is a good
* one, such as when a backup image is restored in error handling.
*
* @return 0 if the recovery image was updated successfully or an error code.
*/
static int firmware_update_apply_recovery_update (const struct firmware_update *updater,
const struct firmware_update_notification *callback, const struct flash *flash,
uint32_t address, size_t img_length, const struct flash *backup, uint32_t backup_addr,
bool *img_good)
{
int status;
debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_RECOVERY_UPDATE, 0, 0);
status = firmware_update_write_image (updater, callback, updater->flash->recovery_flash,
updater->flash->recovery_addr, backup, backup_addr, flash, address, img_length,
UPDATE_STATUS_BACKUP_RECOVERY, UPDATE_STATUS_BACKUP_REC_FAIL, UPDATE_STATUS_UPDATE_RECOVERY,
UPDATE_STATUS_UPDATE_REC_FAIL, img_good);
debug_log_create_entry ((status != 0) ? DEBUG_LOG_SEVERITY_ERROR : DEBUG_LOG_SEVERITY_INFO,
DEBUG_LOG_COMPONENT_CERBERUS_FW, FIRMWARE_LOGGING_RECOVERY_UPDATE_DONE, status, 0);
debug_log_flush ();
return status;
}
/**
* Copy the image from staging flash to active flash.
*
* @param updater The updater to execute.
* @param callback Status callback to report status updates. This can be null to not have status
* reporting.
* @param img_length Length of the image in staging flash.
* @param recovery_updated Output indicating if the recovery image was also updated. This can be
* null if this information is not needed.
*
* @return 0 if active flash was successfully updated or an error code.
*/
static int firmware_update_apply_update (const struct firmware_update *updater,
const struct firmware_update_notification *callback, size_t img_length, bool *recovery_updated)
{
bool img_good;
int allow_update;
int status;
/* Notify the system of an update and see if it should be allowed. */
allow_update = 0;
observable_notify_observers_with_ptr (&updater->state->observable,
offsetof (struct firmware_update_observer, on_update_start), &allow_update);
if (allow_update != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_SYSTEM_PREREQ_FAIL);
return allow_update;
}
/* Save the running application context to restore after reboot. */
firmware_update_status_change (callback, UPDATE_STATUS_SAVING_STATE);
status = updater->context->save (updater->context);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_STATE_SAVE_FAIL);
return status;
}
/* Don't allow the active image to be erased until we have a good recovery image. */
if (updater->flash->recovery_flash && updater->state->recovery_bad) {
status = firmware_update_apply_recovery_update (updater, callback,
updater->flash->staging_flash, updater->flash->staging_addr, img_length, NULL, 0,
&img_good);
if (status != 0) {
return status;
}
updater->state->recovery_bad = !img_good;
if (recovery_updated) {
*recovery_updated = true;
}
}
/* Update the active image from staging flash. */
return firmware_update_write_image (updater, callback, updater->flash->active_flash,
updater->flash->active_addr, updater->flash->backup_flash, updater->flash->backup_addr,
updater->flash->staging_flash, updater->flash->staging_addr, img_length,
UPDATE_STATUS_BACKUP_ACTIVE, UPDATE_STATUS_BACKUP_FAILED, UPDATE_STATUS_UPDATING_IMAGE,
UPDATE_STATUS_UPDATE_FAILED, &img_good);
}
/**
* Check for manifest revocation based on the loaded firmware image. If revocation is indicated,
* process the revocation by updating the recovery image and updated the device state.
*
* This will also update the recovery image when necessary, even if the manifest has not been
* revoked.
*
* @param updater The updater to use for revocation processing.
* @param callback Status callback to report status updates. This can be null to not have status
* reporting.
* @param flash The flash device containing the image to use for recovery updates.
* @param address Base address of the image to use for recovery updates.
* @param img_length Length of the image to use for recovery updates.
* @param new_revision The recovery revision of the loaded image.
* @param recovery_updated Flag indicating if the recovery image has already been updated.
*
* @return 0 if revocation was processed successfully or an error code.
*/
static int firmware_update_process_manifest_revocation (const struct firmware_update *updater,
const struct firmware_update_notification *callback, const struct flash *flash,
uint32_t address, size_t img_length, int new_revision, bool recovery_updated)
{
const struct key_manifest *manifest;
bool img_good;
int manifest_revoked;
int status;
manifest = updater->fw->get_key_manifest (updater->fw);
if (manifest == NULL) {
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_CHK_FAIL);
return FIRMWARE_UPDATE_NO_KEY_MANIFEST;
}
manifest_revoked = manifest->revokes_old_manifest (manifest);
if (ROT_IS_ERROR (manifest_revoked)) {
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_CHK_FAIL);
return manifest_revoked;
}
/* Check if recovery update is necessary. */
firmware_update_status_change (callback, UPDATE_STATUS_CHECK_RECOVERY);
if (manifest_revoked || updater->state->recovery_bad ||
(updater->state->recovery_rev != new_revision)) {
if (updater->flash->recovery_flash && !recovery_updated) {
const struct flash *backup = NULL;
uint32_t backup_addr = 0;
if (!updater->state->recovery_bad) {
if (updater->flash->rec_backup_flash) {
backup = updater->flash->rec_backup_flash;
backup_addr = updater->flash->rec_backup_addr;
}
else {
backup = updater->flash->backup_flash;
backup_addr = updater->flash->backup_addr;
}
}
/* Update the recovery image. */
status = firmware_update_apply_recovery_update (updater, callback, flash, address,
img_length, backup, backup_addr, &img_good);
updater->state->recovery_bad = !img_good;
if (status != 0) {
return status;
}
}
updater->state->recovery_rev = new_revision;
if (manifest_revoked) {
/* Revoke the old manifest. */
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_MANIFEST);
debug_log_create_entry (DEBUG_LOG_SEVERITY_INFO, DEBUG_LOG_COMPONENT_CERBERUS_FW,
FIRMWARE_LOGGING_REVOCATION_UPDATE, 0, 0);
debug_log_flush ();
status = manifest->update_revocation (manifest);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_FAILED);
return status;
}
}
}
return 0;
}
/**
* Run the firmware update process. The firmware update will take the following steps:
* - 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, taking a backup if configured to do so.
* - Copy the image in staging flash to recovery flash, taking a backup if configured to do so,
* if the recovery manifest has been revoked, the recovery revision has changed, or the
* recovery image is known to be bad.
* - 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
* null if no notifications are necessary. Also, individual callbacks that are not desired can be
* left null.
*
* @return 0 if the update completed successfully or an error code.
*/
int firmware_update_run_update (const struct firmware_update *updater,
const struct firmware_update_notification *callback)
{
bool recovery_updated = false;
size_t new_len = 0;
int new_revision;
int status;
if (updater == NULL) {
firmware_update_status_change (callback, UPDATE_STATUS_START_FAILURE);
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
/* If there is no FW header on the image, 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->state->recovery_rev;
/* Verify image in staging flash. */
status = firmware_update_load_and_verify_image (updater, callback,
updater->flash->staging_flash, updater->flash->staging_addr, true, false, true, &new_len,
&new_revision);
if (status != 0) {
return status;
}
/* Apply the update to active flash. */
status = firmware_update_apply_update (updater, callback, new_len, &recovery_updated);
if (status != 0) {
return status;
}
/* 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->state->img_offset);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_REVOKE_CHK_FAIL);
return status;
}
status = firmware_update_process_manifest_revocation (updater, callback,
updater->flash->staging_flash, updater->flash->staging_addr, new_len, new_revision,
recovery_updated);
if (status != 0) {
return status;
}
/* Update completed successfully. */
observable_notify_observers (&updater->state->observable,
offsetof (struct firmware_update_observer, on_update_applied));
return 0;
}
/**
* Run the firmware update process. Only the active image will be updated as part of the process.
* The recovery flash will only be modified if the contents are known to be bad. No revocation
* flows will be executed.
*
* The firmware update will take the following steps:
* - 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.
* - If the recovery flash contains an invalid image, copy the current image in active flash to
* recovery flash. If the current active flash does not contain a valid image or if any
* error is encountered in the recovery restore process, the update will fail. This
* ensures there is never a scenario where both the active and recovery flash both take the
* updated image.
* - Copy the image in staging flash to active flash, taking a backup if configured to do so.
*
* @param updater The updater that should run.
* @param callback A set of notification handlers to use during the update process. This can be
* null if no notifications are necessary. Also, individual callbacks that are not desired can be
* left null.
*
* @return 0 if the update completed successfully or an error code.
*/
int firmware_update_run_update_no_revocation (const struct firmware_update *updater,
const struct firmware_update_notification *callback)
{
size_t new_len = 0;
int new_revision;
int status;
if (updater == NULL) {
firmware_update_status_change (callback, UPDATE_STATUS_START_FAILURE);
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
/* If the recovery image is bad, restore it from the active flash before running the update. */
if (updater->flash->recovery_flash && updater->state->recovery_bad) {
firmware_update_status_change (callback, UPDATE_STATUS_UPDATE_RECOVERY);
status = firmware_update_restore_recovery_image (updater);
debug_log_flush ();
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_UPDATE_REC_FAIL);
return status;
}
}
/* Verify image in staging flash. */
status = firmware_update_load_and_verify_image (updater, callback,
updater->flash->staging_flash, updater->flash->staging_addr, true, false, true, &new_len,
&new_revision);
if (status != 0) {
return status;
}
/* Apply the update to active flash. */
status = firmware_update_apply_update (updater, callback, new_len, NULL);
if (status != 0) {
return status;
}
/* Update completed successfully. */
observable_notify_observers (&updater->state->observable,
offsetof (struct firmware_update_observer, on_update_applied));
return 0;
}
/**
* Execute firmware image revocation based on the image stored in active flash. The revocation
* process involves the following checks:
* - If the recovery image is known to be bad, copy the active flash image to recovery flash.
* - If the image manifest has been revoked, copy the active flash image to recovery flash and
* update the revocation state within the device.
* - If the firmware header indicates a changed recovery revision, copy the active flash image
* to recovery flash.
*
* If none of the checks match the current device state, the function will succeed without doing any
* operation.
*
* @param updater The updater that should execute.
* @param callback A set of notification handlers to use during the update process. This can be
* null if no notifications are necessary. Also, individual callbacks that are not desired can be
* left null.
*
* @return 0 if revocation updates were successful or an error code.
*/
int firmware_update_run_revocation (const struct firmware_update *updater,
const struct firmware_update_notification *callback)
{
size_t new_len = 0;
int new_revision;
int status;
if (updater == NULL) {
firmware_update_status_change (callback, UPDATE_STATUS_START_FAILURE);
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
/* If there is no FW header on the image, 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->state->recovery_rev;
/* Verify image in active flash. */
status = firmware_update_load_and_verify_image (updater, callback, updater->flash->active_flash,
updater->flash->active_addr, false, false, false, &new_len, &new_revision);
if (status != 0) {
return status;
}
/* Check for manifest revocation. */
firmware_update_status_change (callback, UPDATE_STATUS_CHECK_REVOCATION);
return firmware_update_process_manifest_revocation (updater, callback,
updater->flash->active_flash, updater->flash->active_addr, new_len, new_revision, false);
}
/**
* Prepare staging area for incoming FW update file
*
* @param updater Updater to use
* @param size FW update file size to clear in staging area
* @param callback A set of notification handlers to use during the update process. This can be
* null if no notifications are necessary. Also, individual callbacks that are not desired can be
* left null.
*
* @return Preparation status, 0 if success or an error code.
*/
int firmware_update_prepare_staging (const struct firmware_update *updater,
const struct firmware_update_notification *callback, size_t size)
{
int allow_update = 0;
int status;
if (updater == NULL) {
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_PREP_FAIL);
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_PREP);
/* Notify the system than an update is being prepared and see if it should be allowed. */
observable_notify_observers_with_ptr (&updater->state->observable,
offsetof (struct firmware_update_observer, on_prepare_update), &allow_update);
if (allow_update != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_PREP_FAIL);
return allow_update;
}
status = flash_updater_prepare_for_update (&updater->state->update_mgr, size);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_PREP_FAIL);
}
return status;
}
/**
* Program FW update data to staging area
*
* @param updater Updater to use
* @param buf Buffer with FW update data to program
* @param buf_len Length of FW update data buffer
* @param callback A set of notification handlers to use during the update process. This can be
* null if no notifications are necessary. Also, individual callbacks that are not desired can be
* left null.
*
* @return Programming status, 0 if success or an error code.
*/
int firmware_update_write_to_staging (const struct firmware_update *updater,
const struct firmware_update_notification *callback, uint8_t *buf, size_t buf_len)
{
int status;
if ((updater == NULL) || (buf == NULL)) {
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_WRITE_FAIL);
return FIRMWARE_UPDATE_INVALID_ARGUMENT;
}
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_WRITE);
status = flash_updater_write_update_data (&updater->state->update_mgr, buf, buf_len);
if (status != 0) {
firmware_update_status_change (callback, UPDATE_STATUS_STAGING_WRITE_FAIL);
}
return status;
}
/**
* Get the number of bytes remaining in the firmware update currently being received.
*
* @param updater The firmware updater to query.
*
* @return The number of bytes remaining in the current update. This can be negative if more bytes
* have been received than were expected.
*/
int firmware_update_get_update_remaining (const struct firmware_update *updater)
{
if (updater == NULL) {
return 0;
}
return flash_updater_get_remaining_bytes (&updater->state->update_mgr);
}