usb: avoid memory allocations for ota chunk data
What changed, and why it matters
This commit changes how firmware-update (OTA) messages are passed internally in the Blockstream Jade hardware wallet's USB code. Instead of using a helper that allocates extra memory and copies the message, the code now embeds a one-byte source tag at the start of a single fixed buffer and passes that buffer directly. The stated goal is to reduce memory fragmentation. The change itself does not obviously fix a security bug, but it removes a memory-allocation path during firmware updates, which could indirectly reduce the chance of update failures or instability caused by low memory.
Treat as a hardening/refactoring change rather than a confirmed vulnerability fix. Review the implementation of jade_process_push_in_message and jade_process_push_in_message_ex to confirm the new single-buffer path does not introduce off-by-one errors or change ownership semantics. Verify that JADE_OTA_BUF_SIZE matches the previous 4096 expectation and that all callers of the removed _ex function are updated. No urgent security patch is indicated by the diff alone.
Security signals we found
Removes dynamic/secondary memory allocation path from OTA message dispatch
Changes message framing to prepend source byte directly in the same buffer
No explicit bounds check added; relies on existing buffer sizing
OTA code path is security-sensitive because it handles firmware update payloads
Evidence from the diff
The patch modifies three functions in main/usbhmsc/usbmode.c that post OTA-related messages (ota, ota_data, ota_complete). Previously each function declared a local CBOR buffer and called jade_process_push_in_message_ex(cbor_buf, cbor_len, source), which presumably allocated or copied the message. The new code declares a slightly larger local buffer, reserves the first byte for the source, points cbor_buf at buf+1, encodes CBOR into the remaining space, sets buf[0] = source, and calls jade_process_push_in_message(buf, cbor_len + 1). This inlines the source tag and avoids a separate allocation/copy. The title says this prevents fragmentation. There is no explicit bounds-checking change; the buffer sizes are unchanged except for the first-byte offset, and the ota_data buffer is now sized using the JADE_OTA_BUF_SIZE macro instead of a literal 4096.
Changed components
main/usbhmsc/usbmode.cOTA firmware update message posting over USBFunctions: post_ota_message, post_ota_data_message, post_ota_complete_messageInspect captured patch +15 / −10
diff --git a/main/usbhmsc/usbmode.c b/main/usbhmsc/usbmode.c
index 1592588..e7dfb55 100644
--- a/main/usbhmsc/usbmode.c
+++ b/main/usbhmsc/usbmode.c
@@ -426,9 +426,10 @@ static bool post_ota_message(const jade_msg_source_t source, const size_t fwsize
CborEncoder root_map_encoder;
// FIXME: check max size required?
- uint8_t cbor_buf[512 + 128];
+ uint8_t buf[512 + 128];
+ uint8_t* cbor_buf = buf + 1;
const bool has_params = true;
- prepare_common_msg(&root_map_encoder, &root_encoder, source, "ota", cbor_buf, sizeof(cbor_buf), has_params);
+ prepare_common_msg(&root_map_encoder, &root_encoder, source, "ota", cbor_buf, sizeof(buf) - 1, has_params);
CborError cberr = cbor_encode_text_stringz(&root_map_encoder, "params");
JADE_ASSERT(cberr == CborNoError);
@@ -446,8 +447,9 @@ static bool post_ota_message(const jade_msg_source_t source, const size_t fwsize
cberr = cbor_encoder_close_container(&root_encoder, &root_map_encoder);
JADE_ASSERT(cberr == CborNoError);
+ buf[0] = source;
const size_t cbor_len = cbor_encoder_get_buffer_size(&root_encoder, cbor_buf);
- return jade_process_push_in_message_ex(cbor_buf, cbor_len, source);
+ return jade_process_push_in_message(buf, cbor_len + 1);
}
static bool post_ota_data_message(const jade_msg_source_t source, uint8_t* data, size_t data_len)
@@ -456,35 +458,38 @@ static bool post_ota_data_message(const jade_msg_source_t source, uint8_t* data,
JADE_ASSERT(data_len);
// FIXME: check max size required?
- uint8_t cbor_buf[4096 + 128];
+ uint8_t buf[JADE_OTA_BUF_SIZE + 128];
+ uint8_t* cbor_buf = buf + 1;
CborEncoder root_encoder;
CborEncoder root_map_encoder;
const bool has_params = true;
- prepare_common_msg(&root_map_encoder, &root_encoder, source, "ota_data", cbor_buf, sizeof(cbor_buf), has_params);
+ prepare_common_msg(&root_map_encoder, &root_encoder, source, "ota_data", cbor_buf, sizeof(buf) - 1, has_params);
add_bytes_to_map(&root_map_encoder, "params", data, data_len);
const CborError cberr = cbor_encoder_close_container(&root_encoder, &root_map_encoder);
JADE_ASSERT(cberr == CborNoError);
+ buf[0] = source;
const size_t cbor_len = cbor_encoder_get_buffer_size(&root_encoder, cbor_buf);
- return jade_process_push_in_message_ex(cbor_buf, cbor_len, source);
+ return jade_process_push_in_message(buf, cbor_len + 1);
}
static bool post_ota_complete_message(const jade_msg_source_t source)
{
// FIXME: check max size required?
- uint8_t cbor_buf[64];
+ uint8_t buf[64];
+ uint8_t* cbor_buf = buf + 1;
CborEncoder root_encoder;
CborEncoder root_map_encoder; // id, method
const bool has_params = false;
- prepare_common_msg(
- &root_map_encoder, &root_encoder, source, "ota_complete", cbor_buf, sizeof(cbor_buf), has_params);
+ prepare_common_msg(&root_map_encoder, &root_encoder, source, "ota_complete", cbor_buf, sizeof(buf) - 1, has_params);
const CborError cberr = cbor_encoder_close_container(&root_encoder, &root_map_encoder);
JADE_ASSERT(cberr == CborNoError);
+ buf[0] = source;
const size_t cbor_len = cbor_encoder_get_buffer_size(&root_encoder, cbor_buf);
- return jade_process_push_in_message_ex(cbor_buf, cbor_len, source);
+ return jade_process_push_in_message(buf, cbor_len + 1);
}
#define MAX_FW_SIZE_DIGITS 7
Why this scored 23/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.