ota: ensure error replies are sent if firmware sizes mismatch
What changed, and why it matters
This commit fixes a bug in the firmware update (OTA) code for Blockstream Jade hardware wallets. Previously, if the device received an unexpected message after a firmware upload whose size did not match expectations, it could return without sending an error reply to the host. The fix ensures the device always sends an error response in those cases, so the host app knows something went wrong instead of being left hanging.
Treat as a low-to-moderate reliability/security fix. Users should update to a firmware version containing this commit. Developers should review other message-rejection paths in OTA handlers to ensure all error cases send a reply before returning.
Security signals we found
Missing error reply in OTA protocol path
Firmware size mismatch not communicated to host
Host application could be left waiting indefinitely for a response
OTA/delta-OTA process state cleanup inconsistency
Evidence from the diff
In both ota.c and ota_delta.c, the code now checks firmware-size mismatch conditions before setting uploading=false and before expecting the ota_complete message. If ota_complete is not received, the function calls jade_process_reject_message() and returns directly. Previously it jumped to cleanup, but because no error was set in the context on that path, cleanup did not send a reply, leaving the host without a response. The patch also moves the ‘Uploading complete’ flag set after the size checks so that an error reply is still emitted when sizes mismatch.
Changed components
main/process/ota.cmain/process/ota_delta.cBlockstream Jade OTA update protocolInspect captured patch +8 / −8
diff --git a/main/process/ota.c b/main/process/ota.c
index 7bab16b..cb46132 100644
--- a/main/process/ota.c
+++ b/main/process/ota.c
@@ -102,9 +102,6 @@ void ota_process(void* process_ptr)
}
JADE_ASSERT(joctx->validated_confirmed);
- // Uploading complete
- uploading = false;
-
// Bail-out if the fw uncompressed to an unexpected size
if (joctx->remaining_uncompressed != 0) {
JADE_LOGE("Expected uncompressed size: %u, got %u", joctx->firmwaresize,
@@ -118,12 +115,15 @@ void ota_process(void* process_ptr)
goto cleanup;
}
+ // Uploading complete
+ uploading = false;
+
// Expect a complete/request for status
jade_process_load_in_message(process, true);
if (!IS_CURRENT_MESSAGE(process, "ota_complete")) {
// Protocol error
jade_process_reject_message(process, CBOR_RPC_PROTOCOL_ERROR, "Unexpected message, expecting 'ota_complete'");
- goto cleanup;
+ return;
}
// If all good with the upload do all final checks and then finalise the ota
diff --git a/main/process/ota_delta.c b/main/process/ota_delta.c
index 9bfe7a5..83c961e 100644
--- a/main/process/ota_delta.c
+++ b/main/process/ota_delta.c
@@ -184,20 +184,20 @@ void ota_delta_process(void* process_ptr)
goto cleanup;
}
- // Uploading complete
- uploading = false;
-
if (joctx->fwwritten != joctx->firmwaresize) {
joctx->ota_return_status = OTA_ERR_PATCH;
goto cleanup;
}
+ // Uploading complete
+ uploading = false;
+
// Expect a complete/request for status
jade_process_load_in_message(process, true);
if (!IS_CURRENT_MESSAGE(process, "ota_complete")) {
// Protocol error
jade_process_reject_message(process, CBOR_RPC_PROTOCOL_ERROR, "Unexpected message, expecting 'ota_complete'");
- goto cleanup;
+ return;
}
// If all good with the upload do all final checks and then finalise the ota
Why this scored 45/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.