fix(core): limit the offset of translations_read function
What changed, and why it matters
This commit fixes a function that reads translated text data from the hardware wallet's flash storage. Previously, if a caller requested data starting at an offset beyond the end of the translation area, the function would calculate a negative-looking length (because unsigned arithmetic wraps around) and return a pointer to an invalid memory location. The fix checks whether the offset is too large and returns NULL instead. This could have allowed malformed or attacker-controlled data to trick the device into reading memory outside the intended translation area, potentially causing crashes or exposing unrelated flash contents.
Treat as a security hardening fix. Review all callers of translations_read() to ensure they handle NULL returns safely and do not pass unchecked offsets from USB/Protobuf messages. Consider whether translations_area_bytesize() is the correct authoritative size and whether flash_area_get_address() itself enforces bounds. Backport to supported firmware branches if the vulnerable code is present.
Security signals we found
Integer underflow / wraparound in length calculation
Missing bounds check on user-supplied offset
Potential out-of-bounds read from flash area
Return of attacker-influenced pointer/length pair
Defensive patch in security-critical embedded firmware
Evidence from the diff
translations_read() in core/embed/io/translations/translations.c previously computed len = flash_area_get_size(&ASSETS_AREA) - offset without validating offset. Because both operands are uint32_t, an offset larger than the area size causes unsigned integer wraparound, yielding a very large len. The function then returns flash_area_get_address(&ASSETS_AREA, offset, 0), which may point outside the assets/translation area. The patch introduces translations_area_bytesize() as the authoritative size, rejects offset >= size by returning NULL, and only then computes *len = size - offset. This is a bounds-check hardening change.
Changed components
core/embed/io/translations/translations.ctranslations_read()Trezor Core firmware translation/flash I/O layerInspect captured patch +7 / −1
diff --git a/core/embed/io/translations/translations.c b/core/embed/io/translations/translations.c
index 0274422e..c63686b7 100644
--- a/core/embed/io/translations/translations.c
+++ b/core/embed/io/translations/translations.c
@@ -50,7 +50,13 @@ const uint8_t* translations_read(uint32_t* len, uint32_t offset) {
// TODO: _Static_assert was not happy with ASSETS_AREA.num_subareas == 1
// error: expression in static assertion is not constant
assert(ASSETS_AREA.num_subareas == 1);
- *len = flash_area_get_size(&ASSETS_AREA) - offset;
+
+ uint32_t size = translations_area_bytesize();
+ if (offset >= size) {
+ return NULL;
+ }
+ *len = size - offset;
+
return flash_area_get_address(&ASSETS_AREA, offset, 0);
}
Why this scored 64/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.