1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-28 17:18:29 +00:00

core: fix boot loop after uploading invalid homescreen (#1205)

This commit is contained in:
Martin Milata 2020-08-21 12:00:42 +02:00 committed by GitHub
parent 68e119c2c6
commit 1b982659c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 29 deletions

View File

@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- CRW addresses are properly generated. [#1139]
- Fix boot loop after uploading invalid homescreen. [#1118]
### Security
@ -265,6 +266,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[#1089]: https://github.com/trezor/trezor-firmware/issues/1089
[#1098]: https://github.com/trezor/trezor-firmware/issues/1098
[#1115]: https://github.com/trezor/trezor-firmware/issues/1115
[#1118]: https://github.com/trezor/trezor-firmware/issues/1118
[#1126]: https://github.com/trezor/trezor-firmware/issues/1126
[#1139]: https://github.com/trezor/trezor-firmware/issues/1139
[#1159]: https://github.com/trezor/trezor-firmware/issues/1159

View File

@ -349,6 +349,37 @@ void display_icon(int x, int y, int w, int h, const void *data,
}
}
// see docs/misc/toif.md for defintion of the TOIF format
bool display_toif_info(const uint8_t *data, uint32_t len, uint16_t *out_w,
uint16_t *out_h, bool *out_grayscale) {
if (len < 12 || memcmp(data, "TOI", 3) != 0) {
return false;
}
bool grayscale = false;
if (data[3] == 'f') {
grayscale = false;
} else if (data[3] == 'g') {
grayscale = true;
} else {
return false;
}
uint16_t w = *(uint16_t *)(data + 4);
uint16_t h = *(uint16_t *)(data + 6);
uint32_t datalen = *(uint32_t *)(data + 8);
if (datalen != len - 12) {
return false;
}
if (out_w != NULL && out_h != NULL && out_grayscale != NULL) {
*out_w = w;
*out_h = h;
*out_grayscale = grayscale;
}
return true;
}
#if TREZOR_MODEL == T
#include "loader.h"
#endif

View File

@ -77,6 +77,8 @@ void display_bar(int x, int y, int w, int h, uint16_t c);
void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b,
uint8_t r);
bool display_toif_info(const uint8_t *buf, uint32_t len, uint16_t *out_w,
uint16_t *out_h, bool *out_grayscale);
void display_image(int x, int y, int w, int h, const void *data,
uint32_t datalen);
void display_avatar(int x, int y, const void *data, uint32_t datalen,

View File

@ -118,6 +118,33 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_bar_radius_obj,
8, 8,
mod_trezorui_Display_bar_radius);
/// def toif_info(self, image: bytes) -> Tuple[int, int, bool]:
/// """
/// Returns tuple containing TOIF image dimensions: width, height, and
/// whether it is grayscale.
/// Raises an exception for corrupted images.
/// """
STATIC mp_obj_t mod_trezorui_Display_toif_info(mp_obj_t self, mp_obj_t image) {
mp_buffer_info_t buffer = {0};
mp_get_buffer_raise(image, &buffer, MP_BUFFER_READ);
uint16_t w = 0;
uint16_t h = 0;
bool grayscale = false;
bool valid = display_toif_info(buffer.buf, buffer.len, &w, &h, &grayscale);
if (!valid) {
mp_raise_ValueError("Invalid image format");
}
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL));
tuple->items[0] = MP_OBJ_NEW_SMALL_INT(w);
tuple->items[1] = MP_OBJ_NEW_SMALL_INT(h);
tuple->items[2] = mp_obj_new_bool(grayscale);
return MP_OBJ_FROM_PTR(tuple);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_toif_info_obj,
mod_trezorui_Display_toif_info);
/// def image(self, x: int, y: int, image: bytes) -> None:
/// """
/// Renders an image at position (x,y).
@ -131,16 +158,15 @@ STATIC mp_obj_t mod_trezorui_Display_image(size_t n_args,
mp_buffer_info_t image = {0};
mp_get_buffer_raise(args[3], &image, MP_BUFFER_READ);
const uint8_t *data = image.buf;
if (image.len < 8 || memcmp(data, "TOIf", 4) != 0) {
uint16_t w = 0;
uint16_t h = 0;
bool grayscale = false;
bool valid = display_toif_info(data, image.len, &w, &h, &grayscale);
if (!valid || grayscale) {
mp_raise_ValueError("Invalid image format");
}
mp_int_t w = *(uint16_t *)(data + 4);
mp_int_t h = *(uint16_t *)(data + 6);
uint32_t datalen = *(uint32_t *)(data + 8);
if (datalen != image.len - 12) {
mp_raise_ValueError("Invalid size of data");
}
display_image(x, y, w, h, data + 12, datalen);
display_image(x, y, w, h, data + 12, image.len - 12);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_image_obj, 4, 4,
@ -162,21 +188,20 @@ STATIC mp_obj_t mod_trezorui_Display_avatar(size_t n_args,
mp_buffer_info_t image = {0};
mp_get_buffer_raise(args[3], &image, MP_BUFFER_READ);
const uint8_t *data = image.buf;
if (image.len < 8 || memcmp(data, "TOIf", 4) != 0) {
uint16_t w = 0;
uint16_t h = 0;
bool grayscale = false;
bool valid = display_toif_info(data, image.len, &w, &h, &grayscale);
if (!valid || grayscale) {
mp_raise_ValueError("Invalid image format");
}
mp_int_t w = *(uint16_t *)(data + 4);
mp_int_t h = *(uint16_t *)(data + 6);
if (w != AVATAR_IMAGE_SIZE || h != AVATAR_IMAGE_SIZE) {
mp_raise_ValueError("Invalid image size");
}
uint32_t datalen = *(uint32_t *)(data + 8);
if (datalen != image.len - 12) {
mp_raise_ValueError("Invalid size of data");
}
mp_int_t fgcolor = mp_obj_get_int(args[4]);
mp_int_t bgcolor = mp_obj_get_int(args[5]);
display_avatar(x, y, data + 12, datalen, fgcolor, bgcolor);
display_avatar(x, y, data + 12, image.len - 12, fgcolor, bgcolor);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_avatar_obj, 6,
@ -196,15 +221,14 @@ STATIC mp_obj_t mod_trezorui_Display_icon(size_t n_args, const mp_obj_t *args) {
mp_buffer_info_t icon = {0};
mp_get_buffer_raise(args[3], &icon, MP_BUFFER_READ);
const uint8_t *data = icon.buf;
if (icon.len < 8 || memcmp(data, "TOIg", 4) != 0) {
uint16_t w = 0;
uint16_t h = 0;
bool grayscale = false;
bool valid = display_toif_info(data, icon.len, &w, &h, &grayscale);
if (!valid || !grayscale) {
mp_raise_ValueError("Invalid image format");
}
mp_int_t w = *(uint16_t *)(data + 4);
mp_int_t h = *(uint16_t *)(data + 6);
uint32_t datalen = *(uint32_t *)(data + 8);
if (datalen != icon.len - 12) {
mp_raise_ValueError("Invalid size of data");
}
mp_int_t fgcolor = mp_obj_get_int(args[4]);
mp_int_t bgcolor = mp_obj_get_int(args[5]);
display_icon(x, y, w, h, data + 12, icon.len - 12, fgcolor, bgcolor);
@ -545,6 +569,8 @@ STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR_bar), MP_ROM_PTR(&mod_trezorui_Display_bar_obj)},
{MP_ROM_QSTR(MP_QSTR_bar_radius),
MP_ROM_PTR(&mod_trezorui_Display_bar_radius_obj)},
{MP_ROM_QSTR(MP_QSTR_toif_info),
MP_ROM_PTR(&mod_trezorui_Display_toif_info_obj)},
{MP_ROM_QSTR(MP_QSTR_image), MP_ROM_PTR(&mod_trezorui_Display_image_obj)},
{MP_ROM_QSTR(MP_QSTR_avatar), MP_ROM_PTR(&mod_trezorui_Display_avatar_obj)},
{MP_ROM_QSTR(MP_QSTR_icon), MP_ROM_PTR(&mod_trezorui_Display_icon_obj)},

View File

@ -50,6 +50,13 @@ class Display:
are drawn with radius radius.
"""
def toif_info(self, image: bytes) -> Tuple[int, int, bool]:
"""
Returns tuple containing TOIF image dimensions: width, height, and
whether it is grayscale.
Raises an exception for corrupted images.
"""
def image(self, x: int, y: int, image: bytes) -> None:
"""
Renders an image at position (x,y).

View File

@ -12,6 +12,27 @@ if False:
from trezor.messages.ApplySettings import ApplySettings, EnumTypeSafetyCheckLevel
def validate_homescreen(homescreen: bytes) -> None:
if homescreen == b"":
return
if len(homescreen) > storage.device.HOMESCREEN_MAXSIZE:
raise wire.DataError(
"Homescreen is too large, maximum size is {} bytes".format(
storage.device.HOMESCREEN_MAXSIZE
)
)
try:
w, h, grayscale = ui.display.toif_info(homescreen)
except ValueError:
raise wire.DataError("Invalid homescreen")
if w != 144 or h != 144:
raise wire.DataError("Homescreen must be 144x144 pixel large")
if grayscale:
raise wire.DataError("Homescreen must be full-color TOIF image")
async def apply_settings(ctx: wire.Context, msg: ApplySettings):
if not storage.device.is_initialized():
raise wire.NotInitialized("Device is not initialized")
@ -27,8 +48,7 @@ async def apply_settings(ctx: wire.Context, msg: ApplySettings):
raise wire.ProcessError("No setting provided")
if msg.homescreen is not None:
if len(msg.homescreen) > storage.device.HOMESCREEN_MAXSIZE:
raise wire.DataError("Homescreen is too complex")
validate_homescreen(msg.homescreen)
await require_confirm_change_homescreen(ctx)
try:
storage.device.set_homescreen(msg.homescreen)

View File

@ -138,10 +138,7 @@ def get_homescreen() -> Optional[bytes]:
def set_homescreen(homescreen: bytes) -> None:
if len(homescreen) > HOMESCREEN_MAXSIZE:
raise ValueError # homescreen too large
if homescreen[:8] == b"TOIf\x90\x00\x90\x00" or homescreen == b"":
common.set(_NAMESPACE, _HOMESCREEN, homescreen, public=True)
else:
raise ValueError # invalid homescreen
common.set(_NAMESPACE, _HOMESCREEN, homescreen, public=True)
def store_mnemonic_secret(

View File

@ -115,6 +115,39 @@ class TestMsgApplysettings:
assert client.features.passphrase_protection is True
assert client.features.passphrase_always_on_device is False
@pytest.mark.skip_t1
def test_apply_homescreen_toif(self, client):
img = b"TOIf\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV"
with client:
_set_expected_responses(client)
device.apply_settings(client, homescreen=img)
client.set_expected_responses(EXPECTED_RESPONSES_NOPIN)
device.apply_settings(client, homescreen=b"")
@pytest.mark.parametrize(
"toif_data",
[
# incomplete header
b"TOIf\x90\00\x90\x00~"
# wrong magic
b"XXXf\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV"
# wrong datasize in header
b"TOIf\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
# grayscale 144x144
b"TOIg\x90\x00\x90\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV",
# fullcolor 128x128
b"TOIf\x80\x00\x80\x00~\x00\x00\x00\xed\xd2\xcb\r\x83@\x10D\xc1^.\xde#!\xac31\x99\x10\x8aC%\x14~\x16\x92Y9\x02WI3\x01<\xf5cI2d\x1es(\xe1[\xdbn\xba\xca\xe8s7\xa4\xd5\xd4\xb3\x13\xbdw\xf6:\xf3\xd1\xe7%\xc7]\xdd_\xb3\x9e\x9f\x9e\x9fN\xed\xaaE\xef\xdc\xcf$D\xa7\xa4X\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0OV",
],
)
@pytest.mark.skip_ui
@pytest.mark.skip_t1
def test_apply_homescreen_toif_fail(self, client, toif_data):
with pytest.raises(exceptions.TrezorFailure), client:
client.use_pin_sequence([PIN4])
device.apply_settings(client, homescreen=toif_data)
@pytest.mark.skip_t2
def test_apply_homescreen(self, client):
img = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x04\x88\x02\x00\x00\x00\x02\x91\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x90@\x00\x11@\x00\x00\x00\x00\x00\x00\x08\x00\x10\x92\x12\x04\x00\x00\x05\x12D\x00\x00\x00\x00\x00 \x00\x00\x08\x00Q\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x00\x00\x10\x02 \x01\x04J\x00)$\x00\x00\x00\x00\x80\x00\x00\x00\x00\x08\x10\xa1\x00\x00\x02\x81 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\tP\x00\x00\x00\x00\x00\x00 \x00\x00\xa0\x00\xa0R \x12\x84\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x08\x00\tP\x00\x00\x00\x00 \x00\x04 \x00\x80\x02\x00@\x02T\xc2 \x00\x00\x00\x00\x00\x00\x00\x10@\x00)\t@\n\xa0\x80\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x80@\x14\xa9H\x04\x00\x00\x88@\x00\x00\x00\x00\x00\x02\x02$\x00\x15B@\x00\nP\x00\x00\x00\x00\x00\x80\x00\x00\x91\x01UP\x00\x00 \x02\x00\x00\x00\x00\x00\x00\x02\x08@ Z\xa5 \x00\x00\x80\x00\x00\x00\x00\x00\x00\x08\xa1%\x14*\xa0\x00\x00\x02\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xaa\x91 \x00\x05E\x80\x00\x00\x00\x00\x00\x02*T\x05-D\x00\x00\x05 @\x00\x00\x00\x00\x00%@\x80\x11V\xa0\x88\x00\x05@\xb0\x00\x00\x00\x00\x00\x818$\x04\xabD \x00\x06\xa1T\x00\x00\x00\x00\x02\x03\xb8\x01R\xd5\x01\x00\x00\x05AP\x00\x00\x00\x00\x08\xadT\x00\x05j\xa4@\x00\x87ah\x00\x00\x00\x00\x02\x8d\xb8\x08\x00.\x01\x00\x00\x02\xa5\xa8\x10\x00\x00\x00*\xc1\xec \n\xaa\x88 \x02@\xf6\xd0\x02\x00\x00\x00\x0bB\xb6\x14@U"\x80\x00\x01{`\x00\x00\x00\x00M\xa3\xf8 \x15*\x00\x00\x00\x10n\xc0\x04\x00\x00\x02\x06\xc2\xa8)\x00\x96\x84\x80\x00\x00\x1b\x00\x00\x80@\x10\x87\xa7\xf0\x84\x10\xaa\x10\x00\x00D\x00\x00\x02 \x00\x8a\x06\xfa\xe0P\n-\x02@\x00\x12\x00\x00\x00\x00\x10@\x83\xdf\xa0\x00\x08\xaa@\x00\x00\x01H\x00\x05H\x04\x12\x01\xf7\x81P\x02T\t\x00\x00\x00 \x00\x00\x84\x10\x00\x00z\x00@)* \x00\x00\x01\n\xa0\x02 \x05\n\x00\x00\x05\x10\x84\xa8\x84\x80\x00\x00@\x14\x00\x92\x10\x80\x00\x04\x11@\tT\x00\x00\x00\x00\n@\x00\x08\x84@$\x00H\x00\x12Q\x02\x00\x00\x00\x00\x90\x02A\x12\xa8\n\xaa\x92\x10\x04\xa8\x10@\x00\x00\x04\x04\x00\x04I\x00\x04\x14H\x80"R\x01\x00\x00\x00!@\x00\x00$\xa0EB\x80\x08\x95hH\x00\x00\x00\x84\x10 \x05Z\x00\x00(\x00\x02\x00\xa1\x01\x00\x00\x04\x00@\x82\x00\xadH*\x92P\x00\xaaP\x00\x00\x00\x00\x11\x02\x01*\xad\x01\x00\x01\x01"\x11D\x08\x00\x00\x10\x80 \x00\x81W\x80J\x94\x04\x08\xa5 !\x00\x00\x00\x02\x00B*\xae\xa1\x00\x80\x10\x01\x08\xa4\x00\x00\x00\x00\x00\x84\x00\t[@"HA\x04E\x00\x84\x00\x00\x00\x10\x00\x01J\xd5\x82\x90\x02\x00!\x02\xa2\x00\x00\x00\x00\x00\x00\x00\x05~\xa0\x00 \x10\n)\x00\x11\x00\x00\x00\x00\x00\x00!U\x80\xa8\x88\x82\x80\x01\x00\x00\x00\x00\x00\x00H@\x11\xaa\xc0\x82\x00 *\n\x00\x00\x00\x00\x00\x00\x00\x00\n\xabb@ \x04\x00! \x84\x00\x00\x00\x00\x02@\xa5\x15A$\x04\x81(\n\x00\x00\x00\x00\x00\x00 \x01\x10\x02\xe0\x91\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01 \xa9\tQH@\x91 P\x00\x00\x00\x00\x00\x00\x08\x00\x00\xa0T\xa5\x00@\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00 T\xa0\t\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00@\x02\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x10\x00\x00\x10\x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00@\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x08@\x10\x00\x00\x00\x00'

View File

@ -11,6 +11,7 @@
"test_basic.py-test_device_id_same": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_basic.py-test_features": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_basic.py-test_ping": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
"test_msg_applysettings.py-test_apply_homescreen_toif": "d129829fe8c6c30125151b134826b6a11eb96cca2158c1cad951290ac314a5cc",
"test_msg_applysettings.py-test_apply_settings": "2cc8bf660f3be815d19a4bf1265936162a58386fbe632ca4be01541245b79134",
"test_msg_applysettings.py-test_apply_settings_passphrase": "5c1ed9a0be3d14475102d447da0b5d51bbb6dfaaeceff5ea9179064609db7870",
"test_msg_applysettings.py-test_apply_settings_passphrase_on_device": "3e6527e227bdde54f51bc9c417b176d0d87fdb6c40c4761368f50eb201b4beed",