refactor(core/bootloader): separate image upload and image checks
What changed, and why it matters
This commit is a code cleanup in the Trezor bootloader. It moves the generic, image-type-agnostic parts of firmware upload (chunk receiving, retry logic, flash erasing/writing, timeouts) into a new reusable module called wf_image_upload.c, while keeping the firmware-specific checks (signatures, versions, user confirmation, per-chunk hash checks) in wf_firmware_update.c. There is no direct evidence in the diff that this change fixes or introduces a security vulnerability; it appears to be a structural refactor to make the upload code reusable for other image types in the future.
No immediate security action is required. Treat as a normal code-quality refactor. If this refactor is a prerequisite for a future feature (e.g., a new image type using the generic engine), review the new handler implementations when they are added, since any new image type would inherit the engine's assumptions about chunk offsets, erase offsets, and retry behavior.
Security signals we found
Refactor only: logic moved, not changed in security-relevant ways
Same signature/version/model/downgrade checks remain in firmware-specific handler
Same flash erase/write sequence preserved in generic engine
Same chunk retry and hash verification behavior preserved
No changelog entry and no advisory or CVE referenced in commit
Evidence from the diff
The change refactors the bootloader’s firmware-update workflow by splitting it into a generic upload engine (run_image_upload in wf_image_upload.c) and a firmware-specific handler (fw_on_headers/on_chunk/on_finish in wf_firmware_update.c). The engine manages the chunk_buffer, recv_msg_firmware_upload loop, retry budget, flash erase/write, progress UI callbacks, and message timeouts. The handler retains all type-specific logic: vendor/image/secmon header parsing, signature and model/version checks, downgrade protection, interaction-less update policy, user confirmation, and per-chunk hash verification. The public API is a vtable-style image_upload_handler_t. No security-relevant behavior is visibly added or removed; the same validations and writes occur in the same order, just in different functions.
Changed components
Trezor core bootloader firmware update workflowcore/embed/projects/bootloader/workflow/wf_firmware_update.ccore/embed/projects/bootloader/workflow/wf_image_upload.ccore/embed/projects/bootloader/workflow/wf_image_upload.hcore/embed/projects/bootloader/build.rsInspect captured patch +772 / −483
### core/embed/projects/bootloader/build.rs
@@ -28,6 +28,7 @@ fn main() -> Result<()> {
"ui_helpers.c",
"version_check.c",
"workflow/wf_firmware_update.c",
+ "workflow/wf_image_upload.c",
"workflow/wf_wipe_device.c",
"workflow/wf_get_features.c",
"workflow/wf_initialize.c",
### core/embed/projects/bootloader/workflow/wf_firmware_update.c
@@ -38,63 +38,23 @@
#include "bootui.h"
#include "protob/protob.h"
#include "version_check.h"
+#include "wf_image_upload.h"
#include "workflow.h"
#ifdef TREZOR_EMULATOR
#include "emulator.h"
#endif
-#define MESSAGE_RX_TIMEOUT 10000
-
-typedef enum {
- UPLOAD_OK = 0,
- UPLOAD_IN_PROGRESS = 1,
- UPLOAD_ERR_INVALID_CHUNK_SIZE = -1,
- UPLOAD_ERR_INVALID_VENDOR_HEADER = -2,
- UPLOAD_ERR_INVALID_VENDOR_HEADER_SIG = -3,
- UPLOAD_ERR_INVALID_VENDOR_HEADER_MODEL = -15,
- UPLOAD_ERR_INVALID_IMAGE_HEADER = -4,
- UPLOAD_ERR_INVALID_IMAGE_MODEL = -5,
- UPLOAD_ERR_INVALID_IMAGE_HEADER_SIG = -6,
- UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION = -16,
- UPLOAD_ERR_USER_ABORT = -7,
- UPLOAD_ERR_FIRMWARE_TOO_BIG = -8,
- UPLOAD_ERR_INVALID_CHUNK_HASH = -9,
- UPLOAD_ERR_BOOTLOADER_LOCKED = -10,
- UPLOAD_ERR_FIRMWARE_MISMATCH = -11,
- UPLOAD_ERR_NOT_FIRMWARE_UPGRADE = -12,
- UPLOAD_ERR_NOT_FULLTRUST_IMAGE = -13,
- UPLOAD_ERR_INVALID_CHUNK_PADDING = -14,
- UPLOAD_ERR_COMMUNICATION = -17,
- UPLOAD_ERR_INVALID_SECMON_HEADER = -18,
- UPLOAD_ERR_INVALID_SECMON_HEADER_SIG = -19,
- UPLOAD_ERR_INVALID_SECMON_MODEL = -20,
- UPLOAD_ERR_INVALID_SECMON_HASH = -21,
- UPLOAD_ERR_INVALID_SECMON_VERSION = -23,
- UPLOAD_ERR_SECMON_TOO_BIG = -22,
-} upload_status_t;
-
-#define FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT 2
-
-#ifndef TREZOR_EMULATOR
-__attribute__((section(".buf")))
-#endif
-uint32_t chunk_buffer[IMAGE_CHUNK_SIZE / 4];
-
+// Firmware-specific upload handler. Embeds the generic handler vtable plus the
+// state that the firmware validation needs to carry across chunks.
typedef struct {
- uint32_t firmware_remaining; // remaining bytes to upload
- uint32_t firmware_block; // index of currently processed block
- uint32_t chunk_requested; // requested chunk size
- uint32_t erase_offset; // offset of flash memory to erase
- int32_t firmware_upload_chunk_retry; // retry counter
- size_t headers_offset; // offset of headers in the first block
- size_t read_offset; // offset of the next read data in the chunk buffer
- uint32_t chunk_size; // size of already received chunk data
- bool confirmed; // true if the firmware is confirmed by the user
- bool wireless_transport; // whether the transport is over BLE
+ image_upload_handler_t base;
+ image_header hdr; // copy of the received firmware header
+ size_t headers_offset; // offset of the code within the first block
+ // (vhdr.hdrlen + IMAGE_HEADER_SIZE)
#ifdef USE_SECMON_VERIFICATION
- size_t secmon_code_offset; // offset of the secmon code in the first block
- size_t secmon_code_size; // size of the secmon code
+ size_t secmon_code_offset; // offset of the secmon code in the current block
+ size_t secmon_code_size; // size of the secmon code
size_t secmon_code_processed; // size of the processed secmon code
uint8_t expected_secmon_hash[IMAGE_HASH_DIGEST_LENGTH]; // expected hash of
// the secmon code
@@ -105,7 +65,7 @@ typedef struct {
// hashes, this works, but should be fixed by improving the hash_processor
SHA256_CTX secmon_hash_ctx;
#endif
-} firmware_update_ctx_t;
+} fw_upload_handler_t;
static int version_compare(uint32_t vera, uint32_t verb) {
/* Explicit casts so that we control how compiler does the unsigned shift
@@ -173,530 +133,341 @@ static void detect_installation(const vendor_header *current_vhdr,
*keep_seed = sectrue;
}
-static void fw_data_received(size_t len, void *ctx) {
- firmware_update_ctx_t *context = (firmware_update_ctx_t *)ctx;
+static upload_status_t fw_on_headers(image_upload_handler_t *base,
+ protob_io_t *iface, const uint8_t *buf,
+ size_t len) {
+ fw_upload_handler_t *self = (fw_upload_handler_t *)base;
+ (void)len;
- context->chunk_size += len;
- // update loader only after the update is confirmed
- if (context->confirmed) {
- ui_screen_install_progress_upload(
- 1000 *
- (context->firmware_block * IMAGE_CHUNK_SIZE + context->chunk_size) /
- (context->firmware_block * IMAGE_CHUNK_SIZE +
- context->firmware_remaining),
- context->wireless_transport);
- }
-}
+ vendor_header vhdr;
-static upload_status_t process_msg_FirmwareUpload(protob_io_t *iface,
- firmware_update_ctx_t *ctx) {
- FirmwareUpload msg;
+ if (sectrue != read_vendor_header(buf, IMAGE_CHUNK_SIZE, &vhdr)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid vendor header");
+ return UPLOAD_ERR_INVALID_VENDOR_HEADER;
+ }
- const secbool r =
- recv_msg_firmware_upload(iface, &msg, ctx, fw_data_received,
- &((uint8_t *)chunk_buffer)[ctx->read_offset],
- sizeof(chunk_buffer) - ctx->read_offset);
+ if (sectrue != check_vendor_header_model(&vhdr)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError, "Wrong model");
+ return UPLOAD_ERR_INVALID_VENDOR_HEADER_MODEL;
+ }
- if (sectrue != r ||
- ctx->chunk_size != (ctx->chunk_requested + ctx->read_offset)) {
+ if (sectrue != check_vendor_header_keys(&vhdr)) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid chunk size");
- return UPLOAD_ERR_INVALID_CHUNK_SIZE;
+ "Invalid vendor header signature");
+ return UPLOAD_ERR_INVALID_VENDOR_HEADER_SIG;
}
- static image_header hdr;
+ const image_header *received_hdr = read_image_header(
+ buf + vhdr.hdrlen, FIRMWARE_IMAGE_MAGIC, FIRMWARE_MAXSIZE);
- if (ctx->firmware_block == 0) {
- if (ctx->headers_offset == 0) {
- // first block and headers are not yet parsed
- vendor_header vhdr;
+ if (received_hdr != (const image_header *)(buf + vhdr.hdrlen)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid firmware header");
+ return UPLOAD_ERR_INVALID_IMAGE_HEADER;
+ }
- if (sectrue != read_vendor_header((uint8_t *)chunk_buffer,
- IMAGE_CHUNK_SIZE, &vhdr)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid vendor header");
- return UPLOAD_ERR_INVALID_VENDOR_HEADER;
- }
+ if (sectrue != check_image_model(received_hdr)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Wrong firmware model");
+ return UPLOAD_ERR_INVALID_IMAGE_MODEL;
+ }
- if (sectrue != check_vendor_header_model(&vhdr)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Wrong model");
- return UPLOAD_ERR_INVALID_VENDOR_HEADER_MODEL;
- }
+ if (sectrue != check_image_header_sig(received_hdr, vhdr.vsig_m, vhdr.vsig_n,
+ vhdr.vpub)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid firmware signature");
+ return UPLOAD_ERR_INVALID_IMAGE_HEADER_SIG;
+ }
- if (sectrue != check_vendor_header_keys(&vhdr)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid vendor header signature");
- return UPLOAD_ERR_INVALID_VENDOR_HEADER_SIG;
- }
+ if (sectrue != check_firmware_min_version(received_hdr->monotonic)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Firmware downgrade protection");
+ return UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION;
+ }
- const image_header *received_hdr =
- read_image_header((uint8_t *)chunk_buffer + vhdr.hdrlen,
- FIRMWARE_IMAGE_MAGIC, FIRMWARE_MAXSIZE);
+#ifdef USE_SECMON_VERIFICATION
+ size_t secmon_start_offset =
+ (size_t)IMAGE_CODE_ALIGN(vhdr.hdrlen + IMAGE_HEADER_SIZE);
+ size_t secmon_start = (size_t)buf + secmon_start_offset;
+ const secmon_header_t *secmon_hdr =
+ read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
+
+ if (secmon_hdr != NULL) {
+ self->secmon_code_offset =
+ IMAGE_CODE_ALIGN(vhdr.hdrlen + IMAGE_HEADER_SIZE) + SECMON_HEADER_SIZE;
+ }
- if (received_hdr !=
- (const image_header *)((uint8_t *)chunk_buffer + vhdr.hdrlen)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid firmware header");
- return UPLOAD_ERR_INVALID_IMAGE_HEADER;
- }
+ if (secmon_hdr != (const secmon_header_t *)secmon_start) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid secmon header");
+ return UPLOAD_ERR_INVALID_SECMON_HEADER;
+ }
- if (sectrue != check_image_model(received_hdr)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Wrong firmware model");
- return UPLOAD_ERR_INVALID_IMAGE_MODEL;
- }
+ if (sectrue != check_secmon_model(secmon_hdr)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Wrong secmon model");
+ return UPLOAD_ERR_INVALID_SECMON_MODEL;
+ }
- if (sectrue != check_image_header_sig(received_hdr, vhdr.vsig_m,
- vhdr.vsig_n, vhdr.vpub)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid firmware signature");
- return UPLOAD_ERR_INVALID_IMAGE_HEADER_SIG;
- }
+ if (sectrue != check_secmon_header_sig(secmon_hdr)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid secmon signature");
+ return UPLOAD_ERR_INVALID_SECMON_HEADER_SIG;
+ }
- if (sectrue != check_firmware_min_version(received_hdr->monotonic)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Firmware downgrade protection");
- return UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION;
- }
+ if (sectrue != check_secmon_min_version(secmon_hdr->monotonic)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Secmon downgrade protection");
+ return UPLOAD_ERR_INVALID_SECMON_VERSION;
+ }
-#ifdef USE_SECMON_VERIFICATION
- size_t secmon_start_offset =
- (size_t)IMAGE_CODE_ALIGN(vhdr.hdrlen + IMAGE_HEADER_SIZE);
- size_t secmon_start = (size_t)chunk_buffer + secmon_start_offset;
- const secmon_header_t *secmon_hdr =
- read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
-
- if (secmon_hdr != NULL) {
- ctx->secmon_code_offset =
- IMAGE_CODE_ALIGN(vhdr.hdrlen + IMAGE_HEADER_SIZE) +
- SECMON_HEADER_SIZE;
- }
+ self->secmon_code_size = secmon_hdr->codelen;
- if (secmon_hdr != (const secmon_header_t *)secmon_start) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid secmon header");
- return UPLOAD_ERR_INVALID_SECMON_HEADER;
- }
+ memcpy(self->expected_secmon_hash, secmon_hdr->hash,
+ IMAGE_HASH_DIGEST_LENGTH);
+#endif
- if (sectrue != check_secmon_model(secmon_hdr)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Wrong secmon model");
- return UPLOAD_ERR_INVALID_SECMON_MODEL;
- }
+ memcpy(&self->hdr, received_hdr, sizeof(self->hdr));
- if (sectrue != check_secmon_header_sig(secmon_hdr)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid secmon signature");
- return UPLOAD_ERR_INVALID_SECMON_HEADER_SIG;
- }
+ vendor_header current_vhdr;
- if (sectrue != check_secmon_min_version(secmon_hdr->monotonic)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Secmon downgrade protection");
- return UPLOAD_ERR_INVALID_SECMON_VERSION;
- }
+ secbool is_new = secfalse;
+
+ if (sectrue != read_vendor_header((const uint8_t *)FIRMWARE_START,
+ VENDOR_HEADER_MAX_SIZE, ¤t_vhdr)) {
+ is_new = sectrue;
+ }
- ctx->secmon_code_size = secmon_hdr->codelen;
+ const image_header *current_hdr = NULL;
- memcpy(ctx->expected_secmon_hash, secmon_hdr->hash,
- IMAGE_HASH_DIGEST_LENGTH);
-#endif
+ if (is_new == secfalse) {
+ current_hdr =
+ read_image_header((const uint8_t *)FIRMWARE_START + current_vhdr.hdrlen,
+ FIRMWARE_IMAGE_MAGIC, FIRMWARE_MAXSIZE);
- memcpy(&hdr, received_hdr, sizeof(hdr));
+ if (current_hdr !=
+ (const image_header *)(void *)(FIRMWARE_START + current_vhdr.hdrlen)) {
+ is_new = sectrue;
+ }
+ }
- vendor_header current_vhdr;
+ secbool should_keep_seed = secfalse;
+ secbool is_newvendor = secfalse;
+ secbool is_upgrade = secfalse;
+ if (is_new == secfalse) {
+ detect_installation(¤t_vhdr, current_hdr, &vhdr, &self->hdr, &is_new,
+ &should_keep_seed, &is_newvendor, &is_upgrade);
+ }
- secbool is_new = secfalse;
+ secbool is_ilu = secfalse; // interaction-less update
- if (sectrue != read_vendor_header((const uint8_t *)FIRMWARE_START,
- VENDOR_HEADER_MAX_SIZE,
- ¤t_vhdr)) {
- is_new = sectrue;
- }
+ if (bootargs_get_command() == BOOT_COMMAND_INSTALL_UPGRADE) {
+ IMAGE_HASH_CTX ilu_ctx;
+ uint8_t hash[IMAGE_HASH_DIGEST_LENGTH];
+ IMAGE_HASH_INIT(&ilu_ctx);
+ IMAGE_HASH_UPDATE(&ilu_ctx, buf, vhdr.hdrlen + received_hdr->hdrlen);
+ IMAGE_HASH_FINAL(&ilu_ctx, hash);
- const image_header *current_hdr = NULL;
+ // the firmware must be the same as confirmed by the user
+ boot_args_t args = {0};
+ bootargs_get_args(&args);
- if (is_new == secfalse) {
- current_hdr = read_image_header(
- (const uint8_t *)FIRMWARE_START + current_vhdr.hdrlen,
- FIRMWARE_IMAGE_MAGIC, FIRMWARE_MAXSIZE);
+ if (memcmp(args.hash, hash, sizeof(hash)) != 0) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Firmware mismatch");
+ return UPLOAD_ERR_FIRMWARE_MISMATCH;
+ }
- if (current_hdr !=
- (const image_header *)(void *)(FIRMWARE_START +
- current_vhdr.hdrlen)) {
- is_new = sectrue;
- }
- }
+ // the firmware must be from the same vendor
+ // the firmware must be newer
+ if (is_upgrade != sectrue || is_newvendor != secfalse) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Not a firmware upgrade");
+ return UPLOAD_ERR_NOT_FIRMWARE_UPGRADE;
+ }
- secbool should_keep_seed = secfalse;
- secbool is_newvendor = secfalse;
- secbool is_upgrade = secfalse;
- if (is_new == secfalse) {
- detect_installation(¤t_vhdr, current_hdr, &vhdr, &hdr, &is_new,
- &should_keep_seed, &is_newvendor, &is_upgrade);
- }
+ if ((vhdr.vtrust & VTRUST_NO_WARNING) != VTRUST_NO_WARNING) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Not a full-trust image");
+ return UPLOAD_ERR_NOT_FULLTRUST_IMAGE;
+ }
- secbool is_ilu = secfalse; // interaction-less update
-
- if (bootargs_get_command() == BOOT_COMMAND_INSTALL_UPGRADE) {
- IMAGE_HASH_CTX ctx;
- uint8_t hash[IMAGE_HASH_DIGEST_LENGTH];
- IMAGE_HASH_INIT(&ctx);
- IMAGE_HASH_UPDATE(&ctx, (uint8_t *)chunk_buffer,
- vhdr.hdrlen + received_hdr->hdrlen);
- IMAGE_HASH_FINAL(&ctx, hash);
-
- // the firmware must be the same as confirmed by the user
- boot_args_t args = {0};
- bootargs_get_args(&args);
-
- if (memcmp(args.hash, hash, sizeof(hash)) != 0) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Firmware mismatch");
- return UPLOAD_ERR_FIRMWARE_MISMATCH;
- }
-
- // the firmware must be from the same vendor
- // the firmware must be newer
- if (is_upgrade != sectrue || is_newvendor != secfalse) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Not a firmware upgrade");
- return UPLOAD_ERR_NOT_FIRMWARE_UPGRADE;
- }
-
- if ((vhdr.vtrust & VTRUST_NO_WARNING) != VTRUST_NO_WARNING) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Not a full-trust image");
- return UPLOAD_ERR_NOT_FULLTRUST_IMAGE;
- }
-
- // upload the firmware without confirmation
- is_ilu = sectrue;
- }
+ // upload the firmware without confirmation
+ is_ilu = sectrue;
+ }
#if defined LOCKABLE_BOOTLOADER
- if (secfalse != secret_bootloader_locked() &&
- ((vhdr.vtrust & VTRUST_SECRET_MASK) != VTRUST_SECRET_ALLOW)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Install restricted");
- return UPLOAD_ERR_BOOTLOADER_LOCKED;
- }
+ if (secfalse != secret_bootloader_locked() &&
+ ((vhdr.vtrust & VTRUST_SECRET_MASK) != VTRUST_SECRET_ALLOW)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Install restricted");
+ return UPLOAD_ERR_BOOTLOADER_LOCKED;
+ }
#endif
#ifdef USE_SECMON_VERIFICATION
- if (ctx->secmon_code_size >
- ((hdr.codelen + IMAGE_HEADER_SIZE + vhdr.hdrlen) -
- ctx->secmon_code_offset)) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Secmon code too big");
- return UPLOAD_ERR_SECMON_TOO_BIG;
- }
+ if (self->secmon_code_size >
+ ((self->hdr.codelen + IMAGE_HEADER_SIZE + vhdr.hdrlen) -
+ self->secmon_code_offset)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Secmon code too big");
+ return UPLOAD_ERR_SECMON_TOO_BIG;
+ }
#endif
- confirm_result_t response = CANCEL;
- if (((vhdr.vtrust & VTRUST_NO_WARNING) == VTRUST_NO_WARNING) &&
- (sectrue == is_new || sectrue == is_ilu)) {
- // new installation or interaction less updated - auto confirm
- // only allowed for full-trust images
- response = CONFIRM;
- } else {
- if (sectrue != is_new) {
- int version_cmp = version_compare(hdr.version, current_hdr->version);
- response = ui_screen_install_confirm(
- &vhdr, &hdr, should_keep_seed, is_newvendor, is_new, version_cmp);
- } else {
- response = ui_screen_install_confirm(&vhdr, &hdr, sectrue,
- is_newvendor, is_new, 0);
- }
- }
+ confirm_result_t response = CANCEL;
+ if (((vhdr.vtrust & VTRUST_NO_WARNING) == VTRUST_NO_WARNING) &&
+ (sectrue == is_new || sectrue == is_ilu)) {
+ // new installation or interaction less updated - auto confirm
+ // only allowed for full-trust images
+ response = CONFIRM;
+ } else {
+ if (sectrue != is_new) {
+ int version_cmp =
+ version_compare(self->hdr.version, current_hdr->version);
+ response = ui_screen_install_confirm(&vhdr, &self->hdr, should_keep_seed,
+ is_newvendor, is_new, version_cmp);
+ } else {
+ response = ui_screen_install_confirm(&vhdr, &self->hdr, sectrue,
+ is_newvendor, is_new, 0);
+ }
+ }
- if (CONFIRM != response) {
- send_user_abort(iface, "Firmware install cancelled");
- return UPLOAD_ERR_USER_ABORT;
- }
+ if (CONFIRM != response) {
+ send_user_abort(iface, "Firmware install cancelled");
+ return UPLOAD_ERR_USER_ABORT;
+ }
- ui_screen_install_start(ctx->wireless_transport);
- ctx->confirmed = true;
+ ui_screen_install_start(iface->wire->wireless);
- // if firmware is not upgrade, erase storage
- if (sectrue != should_keep_seed) {
+ // if firmware is not upgrade, erase storage
+ if (sectrue != should_keep_seed) {
#ifdef USE_STORAGE_HWKEY
- secret_bhk_regenerate();
+ secret_bhk_regenerate();
#endif
- ensure(erase_storage(NULL), NULL);
+ ensure(erase_storage(NULL), NULL);
#ifdef USE_BACKUP_RAM
- ensure(backup_ram_erase_protected() * sectrue, NULL);
+ ensure(backup_ram_erase_protected() * sectrue, NULL);
#endif
- }
-
- ctx->headers_offset = IMAGE_HEADER_SIZE + vhdr.hdrlen;
- ctx->read_offset = IMAGE_INIT_CHUNK_SIZE;
-
- // request the rest of the first chunk
- uint32_t chunk_limit = (ctx->firmware_remaining > IMAGE_CHUNK_SIZE)
- ? IMAGE_CHUNK_SIZE
- : ctx->firmware_remaining;
- ctx->chunk_requested = chunk_limit - ctx->read_offset;
-
- if (sectrue != send_msg_request_firmware(iface, ctx->read_offset,
- ctx->chunk_requested)) {
- return UPLOAD_ERR_COMMUNICATION;
- }
-
- ctx->firmware_remaining -= ctx->read_offset;
- if (ctx->firmware_remaining > 0) {
- return UPLOAD_IN_PROGRESS;
- }
- return UPLOAD_OK;
- } else {
- // first block with the headers parsed -> the first chunk is now complete
- ctx->read_offset = 0;
- }
}
- // should not happen, but double-check
- if (flash_area_get_address(
- &FIRMWARE_AREA, ctx->firmware_block * IMAGE_CHUNK_SIZE, 0) == NULL) {
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Firmware too big");
- return UPLOAD_ERR_FIRMWARE_TOO_BIG;
- }
+ self->headers_offset = IMAGE_HEADER_SIZE + vhdr.hdrlen;
- if (sectrue !=
- check_single_hash(hdr.hashes + ctx->firmware_block * 32,
- (uint8_t *)chunk_buffer + ctx->headers_offset,
- ctx->chunk_size - ctx->headers_offset)) {
- if (ctx->firmware_upload_chunk_retry > 0) {
- --ctx->firmware_upload_chunk_retry;
+ return UPLOAD_OK;
+}
- // clear chunk buffer
- memset((uint8_t *)&chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE);
- ctx->chunk_size = 0;
+static upload_status_t fw_on_chunk(image_upload_handler_t *base,
+ protob_io_t *iface, uint32_t block_idx,
+ const uint8_t *data, size_t len) {
+ fw_upload_handler_t *self = (fw_upload_handler_t *)base;
- if (sectrue != send_msg_request_firmware(
- iface, ctx->firmware_block * IMAGE_CHUNK_SIZE,
- ctx->chunk_requested)) {
- return UPLOAD_ERR_COMMUNICATION;
- }
- if (ctx->firmware_remaining > 0) {
- return UPLOAD_IN_PROGRESS;
- }
- return UPLOAD_OK;
- }
+ size_t skip = (block_idx == 0) ? self->headers_offset : 0;
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Invalid chunk hash");
+ if (sectrue != check_single_hash(self->hdr.hashes + block_idx * 32,
+ data + skip, len - skip)) {
+ // engine handles retry; do not send a failure message here
return UPLOAD_ERR_INVALID_CHUNK_HASH;
}
#ifdef USE_SECMON_VERIFICATION
// validate secmon code hash
- if (ctx->secmon_code_size > 0) {
- if (ctx->secmon_code_processed == 0) {
- // todo SW SHA256, see comment in firmware_update_ctx_t
- sha256_Init(&ctx->secmon_hash_ctx);
+ if (self->secmon_code_size > 0) {
+ if (self->secmon_code_processed == 0) {
+ // todo SW SHA256, see comment in fw_upload_handler_t
+ sha256_Init(&self->secmon_hash_ctx);
}
size_t secmon_code_remaining =
- ctx->secmon_code_size - ctx->secmon_code_processed;
+ self->secmon_code_size - self->secmon_code_processed;
- size_t secmon_code_to_process = IMAGE_CHUNK_SIZE - ctx->secmon_code_offset;
+ size_t secmon_code_to_process = IMAGE_CHUNK_SIZE - self->secmon_code_offset;
secmon_code_to_process = MIN(secmon_code_to_process, secmon_code_remaining);
- sha256_Update(&ctx->secmon_hash_ctx,
- (uint8_t *)chunk_buffer + ctx->secmon_code_offset,
+ sha256_Update(&self->secmon_hash_ctx, data + self->secmon_code_offset,
secmon_code_to_process);
- ctx->secmon_code_processed += secmon_code_to_process;
- ctx->secmon_code_offset = 0;
+ self->secmon_code_processed += secmon_code_to_process;
+ self->secmon_code_offset = 0;
- if (ctx->secmon_code_processed >= ctx->secmon_code_size) {
+ if (self->secmon_code_processed >= self->secmon_code_size) {
// secmon code is fully processed
uint8_t secmon_hash[IMAGE_HASH_DIGEST_LENGTH];
- sha256_Final(&ctx->secmon_hash_ctx, secmon_hash);
+ sha256_Final(&self->secmon_hash_ctx, secmon_hash);
- if (memcmp(secmon_hash, ctx->expected_secmon_hash,
+ if (memcmp(secmon_hash, self->expected_secmon_hash,
IMAGE_HASH_DIGEST_LENGTH) != 0) {
send_msg_failure(iface, FailureType_Failure_ProcessError,
"Invalid secmon hash");
return UPLOAD_ERR_INVALID_SECMON_HASH;
}
- ctx->secmon_code_size = 0; // reset secmon code size to prevent
+ self->secmon_code_size = 0; // reset secmon code size to prevent
// reprocessing in the next chunk
}
}
-
#endif
- // buffer with the received data
- const uint32_t *src = (const uint32_t *)chunk_buffer;
- // number of received bytes
- uint32_t bytes_remaining = ctx->chunk_size;
- // offset into the FIRMWARE_AREA part of the flash
- uint32_t write_offset = ctx->firmware_block * IMAGE_CHUNK_SIZE;
-
- ensure((ctx->chunk_size % FLASH_BLOCK_SIZE == 0) * sectrue, NULL);
-
- while (bytes_remaining > 0) {
- // erase flash before writing
- uint32_t bytes_erased = 0;
-
- if (write_offset >= ctx->erase_offset) {
- // erase the next flash section
- ensure(flash_area_erase_partial(&FIRMWARE_AREA, ctx->erase_offset,
- &bytes_erased),
- NULL);
- ctx->erase_offset += bytes_erased;
- } else {
- // some erased space left from the previous round => use it
- bytes_erased = ctx->erase_offset - write_offset;
- }
-
- // write the received data
- uint32_t bytes_to_write = MIN(bytes_erased, bytes_remaining);
- ensure(flash_unlock_write(), NULL);
- ensure(flash_area_write_data(&FIRMWARE_AREA, write_offset, src,
- bytes_to_write),
- NULL);
- ensure(flash_lock_write(), NULL);
-
- write_offset += bytes_to_write;
- src += bytes_to_write / sizeof(uint32_t);
-
- bytes_remaining -= bytes_to_write;
- }
-
- ctx->firmware_remaining -= ctx->chunk_requested;
-
- if (ctx->firmware_remaining == 0) {
- // erase the rest (unused part) of the FIRMWARE_AREA
- uint32_t bytes_erased = 0;
- do {
- ensure(flash_area_erase_partial(&FIRMWARE_AREA, ctx->erase_offset,
- &bytes_erased),
- NULL);
- ctx->erase_offset += bytes_erased;
- } while (bytes_erased > 0);
- }
-
- ctx->headers_offset = 0;
- ctx->firmware_block++;
- ctx->firmware_upload_chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT;
-
- if (ctx->firmware_remaining > 0) {
- ctx->chunk_requested = (ctx->firmware_remaining > IMAGE_CHUNK_SIZE)
- ? IMAGE_CHUNK_SIZE
- : ctx->firmware_remaining;
-
- // clear chunk buffer
- ctx->chunk_size = 0;
- memset((uint8_t *)&chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE);
- if (sectrue !=
- send_msg_request_firmware(iface, ctx->firmware_block * IMAGE_CHUNK_SIZE,
- ctx->chunk_requested)) {
- return UPLOAD_ERR_COMMUNICATION;
- }
- } else {
- send_msg_success(iface, NULL);
- }
+ return UPLOAD_OK;
+}
- if (ctx->firmware_remaining > 0) {
- return UPLOAD_IN_PROGRESS;
- }
+static upload_status_t fw_on_finish(image_upload_handler_t *base,
+ protob_io_t *iface) {
+ (void)base;
+ (void)iface;
+ // The firmware image is now live in FIRMWARE_AREA; nothing else to do.
return UPLOAD_OK;
}
-workflow_result_t workflow_firmware_update(protob_io_t *iface) {
- firmware_update_ctx_t ctx = {
- .firmware_upload_chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT,
- };
+static void fw_ui_progress(int permille, bool wireless) {
+ ui_screen_install_progress_upload(permille, wireless);
+}
- FirmwareErase msg;
- secbool res = recv_msg_firmware_erase(iface, &msg);
+static void fw_ui_success(bool wireless) {
+ ui_screen_install_progress_upload(1000, wireless);
+ ui_screen_done(4, sectrue);
+ ui_screen_done(3, secfalse);
+ systick_delay_ms(1000);
+ ui_screen_done(2, secfalse);
+ systick_delay_ms(1000);
+ ui_screen_done(1, secfalse);
+ systick_delay_ms(1000);
+}
- if (res != sectrue) {
- return WF_ERROR;
+static void fw_ui_fail(upload_status_t status) {
+ if (status == UPLOAD_ERR_BOOTLOADER_LOCKED) {
+ // This function does not return
+ show_install_restricted_screen();
+ } else {
+ ui_screen_fail();
}
+}
- ctx.wireless_transport = iface->wire->wireless;
-
- ctx.firmware_remaining = msg.has_length ? msg.length : 0;
- if ((ctx.firmware_remaining > 0) &&
- ((ctx.firmware_remaining % sizeof(uint32_t)) == 0) &&
- (ctx.firmware_remaining <= FIRMWARE_MAXSIZE)) {
- // clear chunk buffer
- memset((uint8_t *)&chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE);
- ctx.chunk_size = 0;
+static const image_upload_ui_t fw_upload_ui = {
+ .progress = fw_ui_progress,
+ .success = fw_ui_success,
+ .fail = fw_ui_fail,
+};
- // request new firmware
- ctx.chunk_requested = (ctx.firmware_remaining > IMAGE_INIT_CHUNK_SIZE)
- ? IMAGE_INIT_CHUNK_SIZE
- : ctx.firmware_remaining;
- if (sectrue != send_msg_request_firmware(iface, 0, ctx.chunk_requested)) {
- ui_screen_fail();
- return WF_ERROR;
- }
- } else {
- // invalid firmware size
- send_msg_failure(iface, FailureType_Failure_ProcessError,
- "Wrong firmware size");
+workflow_result_t workflow_firmware_update(protob_io_t *iface) {
+ FirmwareErase msg;
+ if (sectrue != recv_msg_firmware_erase(iface, &msg)) {
return WF_ERROR;
}
- upload_status_t s = UPLOAD_IN_PROGRESS;
-
- uint32_t msg_deadline = ticks_timeout(MESSAGE_RX_TIMEOUT);
-
- while (true) {
- sysevents_t awaited = {0};
- sysevents_t signalled = {0};
-
- awaited.read_ready = 1 << protob_get_iface_flag(iface);
-
- sysevents_poll(&awaited, &signalled, ticks_timeout(100));
-
- if (awaited.read_ready != signalled.read_ready) {
- if (ticks_expired(msg_deadline)) {
- // timeout
- ui_screen_fail();
- return WF_ERROR;
- }
- continue;
- }
-
- uint16_t msg_id = 0;
-
- if (sectrue != protob_get_msg_header(iface, &msg_id)) {
- // invalid header -> discard
- return WF_ERROR;
- }
- s = process_msg_FirmwareUpload(iface, &ctx);
-
- msg_deadline = ticks_timeout(MESSAGE_RX_TIMEOUT);
+ fw_upload_handler_t handler = {
+ .base =
+ {
+ .target_area = &FIRMWARE_AREA,
+ .max_size = FIRMWARE_MAXSIZE,
+ .success_result = WF_OK_FIRMWARE_INSTALLED,
+ .ui = &fw_upload_ui,
+ .on_headers = fw_on_headers,
+ .on_chunk = fw_on_chunk,
+ .on_finish = fw_on_finish,
+ },
+ };
- if (s < 0 && s != UPLOAD_ERR_USER_ABORT) { // error, but not user abort
- if (s == UPLOAD_ERR_BOOTLOADER_LOCKED) {
- // This function does not return
- show_install_restricted_screen();
- } else {
- ui_screen_fail();
- }
- return WF_ERROR;
- } else if (s == UPLOAD_ERR_USER_ABORT) {
- systick_delay_ms(100);
- return WF_CANCELLED;
- } else if (s == UPLOAD_OK) { // last chunk received
- ui_screen_install_progress_upload(1000, ctx.wireless_transport);
- ui_screen_done(4, sectrue);
- ui_screen_done(3, secfalse);
- systick_delay_ms(1000);
- ui_screen_done(2, secfalse);
- systick_delay_ms(1000);
- ui_screen_done(1, secfalse);
- systick_delay_ms(1000);
- return WF_OK_FIRMWARE_INSTALLED;
- }
- }
+ return run_image_upload(iface, &handler.base,
+ msg.has_length ? msg.length : 0);
}
### core/embed/projects/bootloader/workflow/wf_image_upload.c
@@ -0,0 +1,324 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <sys/flash.h>
+#include <sys/flash_utils.h>
+#include <sys/sysevent.h>
+#include <sys/systick.h>
+
+#include "protob/protob.h"
+#include "wf_image_upload.h"
+#include "workflow.h"
+
+#define MESSAGE_RX_TIMEOUT 10000
+
+#define FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT 2
+
+// Staging buffer for the chunk in flight; see wf_image_upload.h.
+#ifndef TREZOR_EMULATOR
+__attribute__((section(".buf")))
+#endif
+uint32_t chunk_buffer[IMAGE_CHUNK_SIZE / 4];
+
+// Transport-level state of an in-progress upload. Everything here is
+// image-type-agnostic; type-specific state lives in the handler.
+typedef struct {
+ uint32_t image_total;
+ uint32_t remaining; // remaining bytes to upload
+ uint32_t block; // index of currently processed block
+ uint32_t chunk_requested; // requested chunk size
+ uint32_t erase_offset; // offset of flash memory to erase
+ int32_t chunk_retry; // retry counter
+ size_t read_offset; // offset of the next read data in the chunk buffer
+ uint32_t chunk_size; // size of already received chunk data
+ bool headers_parsed; // true once the first chunk's headers are validated
+ bool confirmed; // true once the upload is confirmed by the user
+ bool wireless_transport; // whether the transport is over BLE
+ image_upload_handler_t *handler; // active image-type handler
+} upload_engine_t;
+
+static void upload_data_received(size_t len, void *ctx) {
+ upload_engine_t *e = (upload_engine_t *)ctx;
+
+ e->chunk_size += len;
+ // update loader only after the update is confirmed
+ if (e->confirmed) {
+ e->handler->ui->progress(
+ (int)(1000ULL * (e->block * IMAGE_CHUNK_SIZE + e->chunk_size) /
+ e->image_total),
+ e->wireless_transport);
+ }
+}
+
+static void write_image_data(const flash_area_t *area, uint32_t offset,
+ const uint32_t *data, uint32_t size,
+ uint32_t *erase_offset) {
+ const uint32_t *src = data;
+ uint32_t bytes_remaining = size;
+ uint32_t write_offset = offset;
+
+ while (bytes_remaining > 0) {
+ // erase flash before writing
+ uint32_t bytes_erased = 0;
+
+ if (write_offset >= *erase_offset) {
+ // erase the next flash section
+ ensure(flash_area_erase_partial(area, *erase_offset, &bytes_erased),
+ NULL);
+ *erase_offset += bytes_erased;
+ } else {
+ // some erased space left from the previous round => use it
+ bytes_erased = *erase_offset - write_offset;
+ }
+
+ // write the received data
+ uint32_t bytes_to_write = MIN(bytes_erased, bytes_remaining);
+ ensure(flash_unlock_write(), NULL);
+ ensure(flash_area_write_data(area, write_offset, src, bytes_to_write),
+ NULL);
+ ensure(flash_lock_write(), NULL);
+
+ write_offset += bytes_to_write;
+ src += bytes_to_write / sizeof(uint32_t);
+
+ bytes_remaining -= bytes_to_write;
+ }
+}
+
+static upload_status_t process_upload_chunk(protob_io_t *iface,
+ image_upload_handler_t *handler,
+ upload_engine_t *e) {
+ FirmwareUpload msg;
+
+ const secbool r =
+ recv_msg_firmware_upload(iface, &msg, e, upload_data_received,
+ &((uint8_t *)chunk_buffer)[e->read_offset],
+ sizeof(chunk_buffer) - e->read_offset);
+
+ if (sectrue != r || e->chunk_size != (e->chunk_requested + e->read_offset)) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid chunk size");
+ return UPLOAD_ERR_INVALID_CHUNK_SIZE;
+ }
+
+ if (e->block == 0) {
+ if (!e->headers_parsed) {
+ // first block and headers are not yet parsed -> let the handler validate
+ // all headers, signatures, versions and run user confirmation / policy
+ upload_status_t s = handler->on_headers(
+ handler, iface, (const uint8_t *)chunk_buffer, e->chunk_size);
+ if (s != UPLOAD_OK) {
+ // handler has already sent the failure / abort message
+ return s;
+ }
+
+ e->headers_parsed = true;
+ e->confirmed = true;
+
+ e->read_offset = IMAGE_INIT_CHUNK_SIZE;
+
+ // request the rest of the first chunk
+ uint32_t chunk_limit =
+ (e->remaining > IMAGE_CHUNK_SIZE) ? IMAGE_CHUNK_SIZE : e->remaining;
+ e->chunk_requested = chunk_limit - e->read_offset;
+
+ if (sectrue != send_msg_request_firmware(iface, e->read_offset,
+ e->chunk_requested)) {
+ return UPLOAD_ERR_COMMUNICATION;
+ }
+
+ e->remaining -= e->read_offset;
+ if (e->remaining > 0) {
+ return UPLOAD_IN_PROGRESS;
+ }
+ return UPLOAD_OK;
+ } else {
+ // first block with the headers parsed -> the first chunk is now complete
+ e->read_offset = 0;
+ }
+ }
+
+ // should not happen, but double-check
+ if (flash_area_get_address(
+ handler->target_area,
+ handler->target_offset + e->block * IMAGE_CHUNK_SIZE, 0) == NULL) {
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Firmware too big");
+ return UPLOAD_ERR_FIRMWARE_TOO_BIG;
+ }
+
+ // type-specific per-chunk integrity verification
+ upload_status_t cs = handler->on_chunk(
+ handler, iface, e->block, (const uint8_t *)chunk_buffer, e->chunk_size);
+
+ if (cs == UPLOAD_ERR_INVALID_CHUNK_HASH) {
+ if (e->chunk_retry > 0) {
+ --e->chunk_retry;
+
+ // clear chunk buffer
+ memset((uint8_t *)&chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE);
+ e->chunk_size = 0;
+
+ if (sectrue != send_msg_request_firmware(iface,
+ e->block * IMAGE_CHUNK_SIZE,
+ e->chunk_requested)) {
+ return UPLOAD_ERR_COMMUNICATION;
+ }
+ if (e->remaining > 0) {
+ return UPLOAD_IN_PROGRESS;
+ }
+ return UPLOAD_OK;
+ }
+
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Invalid chunk hash");
+ return UPLOAD_ERR_INVALID_CHUNK_HASH;
+ } else if (cs != UPLOAD_OK) {
+ // handler has already sent its own failure message
+ return cs;
+ }
+
+ ensure((e->chunk_size % FLASH_BLOCK_SIZE == 0) * sectrue, NULL);
+
+ write_image_data(handler->target_area,
+ handler->target_offset + e->block * IMAGE_CHUNK_SIZE,
+ chunk_buffer, e->chunk_size, &e->erase_offset);
+
+ e->remaining -= e->chunk_requested;
+
+ if (e->remaining > 0) {
+ // request the next block
+ e->block++;
+ e->chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT;
+ e->chunk_requested =
+ (e->remaining > IMAGE_CHUNK_SIZE) ? IMAGE_CHUNK_SIZE : e->remaining;
+
+ // clear chunk buffer
+ e->chunk_size = 0;
+ memset((uint8_t *)&chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE);
+ if (sectrue != send_msg_request_firmware(iface, e->block * IMAGE_CHUNK_SIZE,
+ e->chunk_requested)) {
+ return UPLOAD_ERR_COMMUNICATION;
+ }
+ return UPLOAD_IN_PROGRESS;
+ }
+
+ // the whole image is written -> erase the rest (unused part) of the area
+ uint32_t bytes_erased = 0;
+ do {
+ ensure(flash_area_erase_partial(handler->target_area, e->erase_offset,
+ &bytes_erased),
+ NULL);
+ e->erase_offset += bytes_erased;
+ } while (bytes_erased > 0);
+
+ upload_status_t fs = handler->on_finish(handler, iface);
+ if (fs != UPLOAD_OK) {
+ // handler has already sent its own failure message
+ return fs;
+ }
+ send_msg_success(iface, NULL);
+
+ return UPLOAD_OK;
+}
+
+workflow_result_t run_image_upload(protob_io_t *iface,
+ image_upload_handler_t *handler,
+ uint32_t image_size) {
+ upload_engine_t e = {
+ .chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT,
+ .handler = handler,
+ // Start erasing at the base offset so an already-written prefix is
+ // preserved.
+ .erase_offset = handler->target_offset,
+ };
+
+ e.wireless_transport = iface->wire->wireless;
+
+ e.remaining = image_size;
+ e.image_total = image_size;
+ if ((e.remaining > 0) && ((e.remaining % FLASH_BLOCK_SIZE) == 0) &&
+ (e.remaining <= handler->max_size)) {
+ // clear chunk buffer
+ memset((uint8_t *)&chunk_buffer, 0xFF, IMAGE_CHUNK_SIZE);
+ e.chunk_size = 0;
+
+ // request new image
+ e.chunk_requested = (e.remaining > IMAGE_INIT_CHUNK_SIZE)
+ ? IMAGE_INIT_CHUNK_SIZE
+ : e.remaining;
+ if (sectrue != send_msg_request_firmware(iface, 0, e.chunk_requested)) {
+ handler->ui->fail(UPLOAD_ERR_COMMUNICATION);
+ return WF_ERROR;
+ }
+ } else {
+ // invalid image size
+ send_msg_failure(iface, FailureType_Failure_ProcessError,
+ "Wrong firmware size");
+ return WF_ERROR;
+ }
+
+ upload_status_t s = UPLOAD_IN_PROGRESS;
+
+ uint32_t msg_deadline = ticks_timeout(MESSAGE_RX_TIMEOUT);
+
+ while (true) {
+ sysevents_t awaited = {0};
+ sysevents_t signalled = {0};
+
+ awaited.read_ready = 1 << protob_get_iface_flag(iface);
+
+ sysevents_poll(&awaited, &signalled, ticks_timeout(100));
+
+ if (awaited.read_ready != signalled.read_ready) {
+ if (ticks_expired(msg_deadline)) {
+ // timeout
+ handler->ui->fail(UPLOAD_ERR_COMMUNICATION);
+ return WF_ERROR;
+ }
+ continue;
+ }
+
+ uint16_t msg_id = 0;
+
+ if (sectrue != protob_get_msg_header(iface, &msg_id)) {
+ // invalid header -> discard
+ return WF_ERROR;
+ }
+ s = process_upload_chunk(iface, handler, &e);
+
+ msg_deadline = ticks_timeout(MESSAGE_RX_TIMEOUT);
+
+ if (s < 0 && s != UPLOAD_ERR_USER_ABORT) { // error, but not user abort
+ // the handler decides which failure screen to show (and may not return,
+ // e.g. for a locked-bootloader restriction)
+ handler->ui->fail(s);
+ return WF_ERROR;
+ } else if (s == UPLOAD_ERR_USER_ABORT) {
+ systick_delay_ms(100);
+ return WF_CANCELLED;
+ } else if (s == UPLOAD_OK) { // last chunk received
+ handler->ui->success(e.wireless_transport);
+ return handler->success_result;
+ }
+ }
+}
### core/embed/projects/bootloader/workflow/wf_image_upload.h
@@ -0,0 +1,193 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include <sys/flash.h>
+
+#include "protob/protob.h"
+#include "workflow_common.h"
+
+/**
+ * Status of a single step of the chunked image upload.
+ *
+ * Shared between the generic upload engine and the per-image-type handlers.
+ * Each value is fixed, which is why the numbering is not sequential.
+ */
+typedef enum {
+ UPLOAD_OK = 0,
+ UPLOAD_IN_PROGRESS = 1,
+ UPLOAD_ERR_INVALID_CHUNK_SIZE = -1,
+ UPLOAD_ERR_INVALID_VENDOR_HEADER = -2,
+ UPLOAD_ERR_INVALID_VENDOR_HEADER_SIG = -3,
+ UPLOAD_ERR_INVALID_VENDOR_HEADER_MODEL = -15,
+ UPLOAD_ERR_INVALID_IMAGE_HEADER = -4,
+ UPLOAD_ERR_INVALID_IMAGE_MODEL = -5,
+ UPLOAD_ERR_INVALID_IMAGE_HEADER_SIG = -6,
+ UPLOAD_ERR_INVALID_IMAGE_HEADER_VERSION = -16,
+ UPLOAD_ERR_USER_ABORT = -7,
+ UPLOAD_ERR_FIRMWARE_TOO_BIG = -8,
+ UPLOAD_ERR_INVALID_CHUNK_HASH = -9,
+ UPLOAD_ERR_BOOTLOADER_LOCKED = -10,
+ UPLOAD_ERR_FIRMWARE_MISMATCH = -11,
+ UPLOAD_ERR_NOT_FIRMWARE_UPGRADE = -12,
+ UPLOAD_ERR_NOT_FULLTRUST_IMAGE = -13,
+ UPLOAD_ERR_INVALID_CHUNK_PADDING = -14,
+ UPLOAD_ERR_COMMUNICATION = -17,
+ UPLOAD_ERR_INVALID_SECMON_HEADER = -18,
+ UPLOAD_ERR_INVALID_SECMON_HEADER_SIG = -19,
+ UPLOAD_ERR_INVALID_SECMON_MODEL = -20,
+ UPLOAD_ERR_INVALID_SECMON_HASH = -21,
+ UPLOAD_ERR_INVALID_SECMON_VERSION = -23,
+ UPLOAD_ERR_SECMON_TOO_BIG = -22,
+} upload_status_t;
+
+// Single staging buffer shared by the upload engine: one IMAGE_CHUNK_SIZE chunk
+// is received here, verified by the handler, then written to flash.
+//
+// May also be borrowed as scratch before an upload starts, but the borrow does
+// not survive one: run_image_upload() overwrites the whole buffer, so neither
+// its contents nor a pointer into it may be read afterwards. Bound such a
+// receive by the object's own maximum size, not by IMAGE_CHUNK_SIZE.
+extern uint32_t chunk_buffer[];
+
+typedef struct image_upload_handler image_upload_handler_t;
+
+/**
+ * Type-specific UI callbacks for an image upload.
+ *
+ * The engine never draws screens itself; it only signals progress, success and
+ * failure, and each image type renders its own UI.
+ */
+typedef struct {
+ /**
+ * Renders upload / installation progress.
+ *
+ * @param permille Progress in the range 0..1000.
+ * @param wireless True if the transport is wireless (BLE).
+ */
+ void (*progress)(int permille, bool wireless);
+
+ /**
+ * Renders the success / completion sequence after the image is installed.
+ *
+ * @param wireless True if the transport is wireless (BLE).
+ */
+ void (*success)(bool wireless);
+
+ /**
+ * Renders the failure screen for a terminal upload status. May be noreturn
+ * for some statuses (e.g. a locked-bootloader restriction screen).
+ *
+ * @param status The terminal status that aborted the upload.
+ */
+ void (*fail)(upload_status_t status);
+} image_upload_ui_t;
+
+/**
+ * Per-image-type strategy plugged into the generic upload engine.
+ *
+ * The engine owns the transport (erase/length handshake, chunk request/receive
+ * loop, retries, timeout, progress UI) and the flash erase+write into
+ * `target_area`. The handler owns everything type-specific: header parsing and
+ * signature/version/model validation, user confirmation and policy, per-chunk
+ * integrity, and finalization.
+ *
+ * Failure-message contract:
+ * - `on_headers` and `on_finish` send their own specific failure / abort
+ * message before returning a negative status.
+ * - `on_chunk` returns UPLOAD_ERR_INVALID_CHUNK_HASH *without* sending a
+ * message (the engine may retry the block and only reports failure once the
+ * retry budget is exhausted). For any other negative status, `on_chunk`
+ * sends its own message first.
+ */
+struct image_upload_handler {
+ /** Destination flash area the image is written to. */
+ const flash_area_t *target_area;
+ /** Base byte offset within `target_area` to write the image at (default 0),
+ * so an image can be staged after an already-written prefix. The image goes
+ * to `target_offset + <image offset>`; the host-facing chunk offsets
+ * (FirmwareRequest) stay image-relative (0-based). */
+ uint32_t target_offset;
+ /** Upper bound on the declared image size, in bytes. */
+ uint32_t max_size;
+ /** Workflow result returned on a successful upload. */
+ workflow_result_t success_result;
+ /** Type-specific UI callbacks. */
+ const image_upload_ui_t *ui;
+
+ /**
+ * Validates the image headers and runs user confirmation / policy.
+ *
+ * Called once with the first IMAGE_INIT_CHUNK_SIZE bytes, which contain all
+ * headers. On success the engine fetches the remainder of the image.
+ *
+ * @param self Handler instance.
+ * @param iface Protobuf I/O interface used to send failure / abort messages.
+ * @param buf Buffer holding the first received chunk.
+ * @param len Number of valid bytes in @p buf.
+ * @return UPLOAD_OK on success, a negative upload_status_t otherwise.
+ */
+ upload_status_t (*on_headers)(image_upload_handler_t *self,
+ protob_io_t *iface, const uint8_t *buf,
+ size_t len);
+
+ /**
+ * Verifies a fully-received chunk before it is written to flash.
+ *
+ * @param self Handler instance.
+ * @param iface Protobuf I/O interface used to send failure messages.
+ * @param block_idx Zero-based index of the chunk within the image.
+ * @param data Buffer holding the received chunk.
+ * @param len Number of valid bytes in @p data.
+ * @return UPLOAD_OK on success; UPLOAD_ERR_INVALID_CHUNK_HASH to let the
+ * engine retry the block; another negative upload_status_t to fail.
+ */
+ upload_status_t (*on_chunk)(image_upload_handler_t *self, protob_io_t *iface,
+ uint32_t block_idx, const uint8_t *data,
+ size_t len);
+
+ /**
+ * Finalizes the upload after the last chunk is verified and written.
+ *
+ * @param self Handler instance.
+ * @param iface Protobuf I/O interface used to send failure messages.
+ * @return UPLOAD_OK on success, a negative upload_status_t otherwise.
+ */
+ upload_status_t (*on_finish)(image_upload_handler_t *self,
+ protob_io_t *iface);
+};
+
+/**
+ * Runs the chunked image upload driven by @p handler.
+ *
+ * Streams and writes the image to the handler's target area, and drives
+ * per-chunk verification and finalization. The caller is responsible for the
+ * type-specific erase/length handshake and passes the declared image size in.
+ *
+ * @param iface Protobuf I/O interface to communicate with the host.
+ * @param handler Per-image-type handler describing validation and destination.
+ * @param image_size Declared total image size, in bytes.
+ * @return The workflow result.
+ */
+workflow_result_t run_image_upload(protob_io_t *iface,
+ image_upload_handler_t *handler,
+ uint32_t image_size);Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.