Headline
CVE-2023-23305: garmin-ciq-app-research/CVE-2023-23305.md at main · anvilsecure/garmin-ciq-app-research
The GarminOS TVM component in CIQ API version 1.0.0 through 4.1.7 is vulnerable to various buffer overflows when loading binary resources. A malicious application embedding specially crafted resources could hijack the execution of the device’s firmware.
Buffer Overflows in Font Resources
It is possible to embed fonts to a PRG file by specifying them in the font.xml resource file. The fonts can later be retrieved using WatchUi.loadResource(Rez.Fonts.myFont); in the application.
The firmware supports two types of font:
- A legacy non-unicode font format that is no longer generated when compiling a PRG application but still supported inside the firmware
- A unicode font format
During compilation, the original font files are stored alongside a custom header that defines several properties. For instance, for non-unicode font, the following header is prefixed to the font data:
Index
Size in bytes
Name
Value
0x00
4
sentinel
0x0000f23b
0x04
4
height
0x08
4
glyph count
0x0c
4
min
0x10
2
data size
0x12
3 * glyph count
glyph table buffer
n
4
glyph sentinel
n + 4
1 * data size
extra data buffer
When computing the buffers that will hold the font data, integer overflows can occur resulting in an allocated buffer being smaller than expected. When the font data is then copied, the firmware writes out of bounds.
e_tvm_error _tvm_app_load_resource(s_tvm_ctx *ctx,int fd,uint app_type,s_tvm_object *resource,s_tvm_object *out) { // […] // Anvil: Read header from the font resource eVar1 = file_read_4bytes(fd,&font_height); psVar2 = (uint)eVar1; if (psVar2 != 0x0) goto LAB_047ac2c6; eVar1 = file_read_4bytes(fd,(uint *)&font_glyph_count); // […] if ((psVar2 != (s_tvm_ctx *)0x0) || (psVar2 = file_read_2bytes?(fd,&font_data_size), psVar2 != 0x0)) goto LAB_047ac2c6; // Anvil: Computing the size of the buffer to allocate. // Anvil: This can overflow, resulting in the size being smaller than expected size_glyph_table_buffer = (font_data_size & 0xffff) + (int)font_glyph_count * 4 + 0x34; // […] // Anvil: Allocating the buffer, which could be smaller than expected tvm_mem_alloc(ctx,size_glyph_table_buffer,0,&glyph_table); // […] // Anvil: Read `font_glyph_count` bytes from the font resource and copy them to the buffer. Since the buffer is smaller than expected, it writes out-of-bounds. for (i_glyph = (s_tvm_ctx *)psVar2; i_glyph < font_glyph_count; i_glyph = (s_tvm_ctx *)((int)&i_glyph->unknown-? + 1)) { if ((s_tvm_ctx *)psVar2 == (s_tvm_ctx *)0x0) { p_current_glyph = &p_glyph_table_data->glyphs_buffer + (int)i_glyph; while( true ) { // Anvil: Out-of-bound write psVar2 = file_read_2bytes(fd,p_current_glyph); if ((s_tvm_ctx *)psVar2 != (s_tvm_ctx *)0x0) break; iVar2 = file_read-?(fd,&sentinel,1); if (iVar2 != 1) { // […] } // Anvil: Out-of-bound write *(undefined *)((int)p_current_glyph + 3) = sentinel._0_1_; i_glyph = (s_tvm_ctx *)((int)&i_glyph->unknown-? + 1); p_current_glyph = p_current_glyph + 1; if (font_glyph_count <= i_glyph) goto LAB_047ac98a; } } } // […] }
The same vulnerable pattern can be seen for unicode fonts. Although the header is different, the firmware computes the size of the buffer to allocate based on the values specified in the header, which can overflow and result in an allocated buffer smaller than expected.
It is possible to trigger the crash by compiling a PRG application and editing its font resource to change the header values with:
- Glyph count: 0x4000001A
- Font data size: 0x108
The computed buffer size will be: (0x108 & 0xffff) + 0x4000001A * 4 + 0x34 = 0x1000001a4. Since the registers can only hold 32-bit values, it gets truncated to 0x1000001a4 & 0xffffffff = 0x1a4. The firmware will then attempt to copy 0x4000001A glyphs to a buffer of 0x1a4 bytes.
The following proof-of-concept triggers the vulnerability: https://github.com/anvilsecure/garmin-ciq-app-research/blob/main/poc/GRMN-06.prg