ota: add parse_uint32 and use it to parse firmware file size
What changed, and why it matters
This commit replaces a hand-rolled string-to-number conversion when reading a firmware file's size from its filename with a stricter, shared helper function. The old code copied digits into a small temporary buffer and converted them with strtoul, which could mishandle very large numbers or edge cases. The new code validates that the value fits in a 32-bit unsigned integer and enforces the same maximum firmware size limit. It is a hardening/correctness improvement rather than a clear-cut fix for an actively exploitable vulnerability.
Treat as a defensive hardening patch. Review whether the OTA filename parser is reachable from untrusted input (e.g., USB mass-storage filenames supplied by a host) and verify that downstream consumers of read_fwsize() reject a returned size of 0. Consider whether parse_uint64() itself is robust against str_len of zero and all-numeric inputs, since parse_uint32() delegates to it.
Security signals we found
Replaces strncpy/strtoul parsing with a bounded, overflow-aware integer parser
Adds explicit 32-bit range validation for firmware size parsed from untrusted filename
Removes a small stack buffer used during filename parsing, reducing local buffer-management risk
Hardens OTA firmware size handling, a security-sensitive boot/update path
Evidence from the diff
The change removes a MAX_FW_SIZE_DIGITS macro and a strncpy/strtoul based parser in read_fwsize() inside main/usbhmsc/usbmode.c. It introduces parse_uint32() in main/utils/util.c, built on the existing parse_uint64() helper, and uses it to parse the firmware size embedded between underscores in the OTA filename. parse_uint32() rejects non-digit characters, overflow beyond UINT32_MAX, and values above the 9,999,999 byte firmware cap. This eliminates reliance on strtoul’s error handling and removes a fixed-size stack buffer used for temporary copying.
Changed components
main/usbhmsc/usbmode.cmain/utils/util.cmain/utils/util.hOTA firmware update filename parsingInspect captured patch +17 / −9
diff --git a/main/usbhmsc/usbmode.c b/main/usbhmsc/usbmode.c
index e49a9f0..f8fd07d 100644
--- a/main/usbhmsc/usbmode.c
+++ b/main/usbhmsc/usbmode.c
@@ -471,8 +471,6 @@ static bool post_ota_complete_message(const jade_msg_source_t source)
return jade_process_push_in_message(buf, cbor_len + 1);
}
-#define MAX_FW_SIZE_DIGITS 7
-
static size_t read_fwsize(const char* str)
{
JADE_ASSERT(str);
@@ -494,17 +492,14 @@ static size_t read_fwsize(const char* str)
return 0;
}
+ const uint64_t MAX_FW_SIZE = 9999999;
const char* start = second_last_underscore + 1;
const char* end = last_underscore;
- if (end == start || (end - start) > MAX_FW_SIZE_DIGITS) {
+ uint32_t fwsize;
+ if (!parse_uint32(start, end - start, &fwsize) || fwsize > MAX_FW_SIZE) {
return 0;
}
-
- char temp[MAX_FW_SIZE_DIGITS + 1]; // Maximum digits plus null terminator
- strncpy(temp, start, end - start);
- temp[end - start] = '\0';
-
- return strtoul(temp, NULL, 10);
+ return fwsize;
}
static bool read_hash_file_to_buffer(const char* filename, uint8_t* buffer, size_t buf_size)
diff --git a/main/utils/util.c b/main/utils/util.c
index 22d5f9f..e741988 100644
--- a/main/utils/util.c
+++ b/main/utils/util.c
@@ -199,4 +199,14 @@ bool parse_uint64(const char* str, const size_t str_len, uint64_t* value_out)
*value_out = value;
return true;
}
+
+bool parse_uint32(const char* str, const size_t str_len, uint32_t* value_out)
+{
+ uint64_t value;
+ if (!parse_uint64(str, str_len, &value) || value > 0xffffffff) {
+ return false;
+ }
+ *value_out = value & 0xffffffff;
+ return true;
+}
#endif // AMALGAMATED_BUILD
diff --git a/main/utils/util.h b/main/utils/util.h
index f45301b..076a447 100644
--- a/main/utils/util.h
+++ b/main/utils/util.h
@@ -101,6 +101,9 @@ void split_text(
// Parse a uint64 from a string. Allows leading zeros but no non-digit chars
bool parse_uint64(const char* str, size_t str_len, uint64_t* value_out);
+// As for parse_uint64 but for 32 bit integers
+bool parse_uint32(const char* str, size_t str_len, uint32_t* value_out);
+
// Bip32 path utils
static inline bool ishardened(const uint32_t n) { return n & 0x80000000; }
static inline uint32_t harden(const uint32_t n) { return n | 0x80000000; }
Why this scored 40/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.