ota: be more careful formatting ota versions for user display
What changed, and why it matters
This commit tightens how firmware version strings are built and shown to the user during over-the-air (OTA) updates on Blockstream Jade hardware wallets. It removes a risky hand-rolled string-lowercase function, adds compile-time checks that string constants fit in their buffers, and prevents configuring a firmware that cannot actually be OTA flashed. The main security benefit is reducing the chance that a malformed or mismatched firmware image could confuse the user or crash the device during the update confirmation screen.
Treat as a defensive hardening patch. Review the full change (including any code not in the supplied diff) to confirm that 'disallow configuring a firmware that can't be OTA flashed' is enforced. Continue normal firmware signing and downgrade-protection checks, and consider whether the new lowercasing loop needs a length check if custom_info->config is not NUL-terminated.
Security signals we found
Removal of custom to_lower() with implicit buffer-size assumption
Addition of JADE_STATIC_ASSERT bounds checks before strcmp on fixed-size fields
Explicit null termination after lowercasing custom_info->config
Compile-time lower-case config string avoids runtime copy and potential truncation
Commit message frames change as hardening OTA version display and disallowing non-OTA firmware configuration
Evidence from the diff
The patch refactors ota_user_validate() in main/process/ota_util.c. It deletes a custom to_lower() helper that assumed the destination was at least strlen(src)+1 bytes, replacing it with an explicit in-place lowercase loop over custom_info->config plus a guaranteed null terminator. It also switches the current-version display string from a runtime lowercase copy of JADE_OTA_CONFIG to a compile-time string literal JADE_OTA_CONFIG_LOWER, and adds JADE_STATIC_ASSERT guards so strcmp() against fixed constants cannot read past the custom_info fields. A new ota_defines.h macro provides the lower-case variant of the radio config string. The commit message says it also ‘disallow[s] configuring a firmware that can’t be OTA flashed,’ but that logic is not visible in the diff hunk supplied; it may be implied by the stricter validation or reside in code not shown.
Changed components
main/process/ota_util.cmain/process/ota_defines.hJade OTA user-validation flowFirmware version display activity (show_ota_versions_activity)Inspect captured patch +24 / −21
diff --git a/main/process/ota_defines.h b/main/process/ota_defines.h
index 57c3382..bb15412 100644
--- a/main/process/ota_defines.h
+++ b/main/process/ota_defines.h
@@ -11,8 +11,10 @@
// Whether the ble/radio is configured/enabled
#ifdef CONFIG_BT_ENABLED
#define JADE_OTA_CONFIG "BLE"
+#define JADE_OTA_CONFIG_LOWER "ble"
#else
#define JADE_OTA_CONFIG "NORADIO"
+#define JADE_OTA_CONFIG_LOWER "noradio"
#endif
// Board type - Production Jade (1.0, 1.1, etc.),
diff --git a/main/process/ota_util.c b/main/process/ota_util.c
index ac40736..5eaa209 100644
--- a/main/process/ota_util.c
+++ b/main/process/ota_util.c
@@ -397,15 +397,6 @@ error:
}
}
-// NOTE: 'dest' is assumed to be at least as long as 'strlen(src)'
-static void to_lower(char* dest, const char* src)
-{
- while (*src) {
- *dest++ = tolower(*src++);
- }
- *dest = '\0';
-}
-
void ota_user_validate(jade_ota_ctx_t* joctx, const uint8_t* uncompressed)
{
JADE_ASSERT(joctx);
@@ -444,12 +435,14 @@ void ota_user_validate(jade_ota_ctx_t* joctx, const uint8_t* uncompressed)
// 'Board Type' and 'Features' must match.
// 'Config' is allowed to differ.
+ JADE_STATIC_ASSERT(sizeof(JADE_OTA_BOARD_TYPE) <= sizeof(custom_info->board_type));
if (strcmp(JADE_OTA_BOARD_TYPE, custom_info->board_type)) {
JADE_LOGE("Firmware board type mismatch %s %s", JADE_OTA_BOARD_TYPE, custom_info->board_type);
joctx->ota_return_status = OTA_ERR_INVALIDFW;
return;
}
+ JADE_STATIC_ASSERT(sizeof(JADE_OTA_FEATURES) <= sizeof(custom_info->features));
if (strcmp(JADE_OTA_FEATURES, custom_info->features)) {
JADE_LOGE("Firmware features mismatch");
joctx->ota_return_status = OTA_ERR_INVALIDFW;
@@ -457,22 +450,30 @@ void ota_user_validate(jade_ota_ctx_t* joctx, const uint8_t* uncompressed)
}
// User to confirm once new firmware version known and all checks passed
- char current_config[sizeof(JADE_OTA_CONFIG)];
- to_lower(current_config, JADE_OTA_CONFIG);
- char current_version[sizeof(running_app_info.version) + sizeof(current_config) + 2];
- int rc = snprintf(current_version, sizeof(current_version), "%s %s", running_app_info.version, current_config);
- JADE_ASSERT(rc > 0 && rc < sizeof(current_version));
-
- char new_config[sizeof(custom_info->config)];
- to_lower(new_config, custom_info->config);
- char new_version[sizeof(new_app_info->version) + sizeof(new_config) + 2];
- rc = snprintf(new_version, sizeof(new_version), "%s %s", new_app_info->version, new_config);
- JADE_ASSERT(rc > 0 && rc < sizeof(new_version));
+ char current_ver[sizeof(running_app_info.version) + sizeof(JADE_OTA_CONFIG_LOWER) + 2];
+ int rc = snprintf(current_ver, sizeof(current_ver), "%s " JADE_OTA_CONFIG_LOWER, running_app_info.version);
+ JADE_ASSERT(rc > 0 && rc < sizeof(current_ver));
+
+ char new_config[sizeof(custom_info->config) + 1];
+ for (size_t i = 0; i < sizeof(custom_info->config); ++i) {
+ new_config[i] = tolower((unsigned char)custom_info->config[i]);
+ if (!new_config[i]) {
+ break;
+ }
+ }
+ new_config[sizeof(new_config) - 1] = '\0';
+ char new_version[sizeof(new_app_info->version) + 1];
+ memcpy(new_version, new_app_info->version, sizeof(new_app_info->version));
+ new_version[sizeof(new_version) - 1] = '\0';
+
+ char new_ver[sizeof(new_version) + sizeof(new_config) + 2];
+ rc = snprintf(new_ver, sizeof(new_ver), "%s %s", new_version, new_config);
+ JADE_ASSERT(rc > 0 && rc < sizeof(new_ver));
const bool full_fw_hash = joctx->hash_type == HASHTYPE_FULLFWDATA;
// Ask user to confirm
- if (!show_ota_versions_activity(current_version, new_version, joctx->expected_hash_hexstr, full_fw_hash)) {
+ if (!show_ota_versions_activity(current_ver, new_ver, joctx->expected_hash_hexstr, full_fw_hash)) {
JADE_LOGW("User declined ota firmware version");
joctx->ota_return_status = OTA_ERR_USERDECLINED;
return;
Why this scored 35/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.