refactor(core): do not use mini_snprintf in board/bootloader
What changed, and why it matters
This commit is a code cleanup that replaces the use of a small printf-style formatter (mini_snprintf) with simpler, purpose-built string helpers in the Trezor bootloader and low-level system code. It does not add or remove security features, and there is no direct evidence it fixes a specific vulnerability. The change may reduce the attack surface by removing format-string parsing from some paths, but it also introduces new helper functions whose correctness now matters for safety.
Review the new strutils helpers for correct null-termination and bounds handling, especially cstr_append and the integer append variants. Verify that all refactored display paths still produce identical output and that no truncation changes user-visible security warnings. Treat as a hardening/refactor commit unless additional context shows it fixes a specific vulnerability.
Security signals we found
Removal of mini_snprintf from bootloader/boardloader reduces format-string parsing in privileged boot stages
New cstr_append helpers are now used in security-relevant display paths (RSOD, wipe-code screen, PIN-attempt screen, fault messages)
Potential for off-by-one or truncation bugs in newly introduced cstr_append and integer append helpers
No explicit security bug or CVE mentioned in commit message or diff
Refactor touches low-trust display-only output, not cryptographic or authorization logic
Evidence from the diff
The patch removes mini_snprintf/mini_vsnprintf usage from boardloader, bootloader, error handling, fault messaging, and RSOD (red screen of death) code. It introduces and uses cstr_append, cstr_append_int32, cstr_append_uint32, and cstr_append_uint32_hex in a new strutils module. Several call sites are refactored to build strings by concatenation instead of format strings. The terminal API is split into term_nprint, term_print, term_print_int32, and term_printf. The change is described by the vendor as a refactor with no changelog entry.
Changed components
core/embed/rtl/strutils.ccore/embed/rtl/inc/rtl/strutils.hcore/embed/gfx/terminal.ccore/embed/projects/boardloader/sd_update.ccore/embed/projects/bootloader/bootui.ccore/embed/projects/bootloader/wire/wire_iface_ble.ccore/embed/projects/bootloader_ci/bootui.ccore/embed/projects/bootloader_ci/main.ccore/embed/rtl/error_handling.ccore/embed/sys/task/stm32/system.ccore/embed/util/rsod/rsod.ccore/SConscript.boardloadercore/SConscript.bootloadercore/SConscript.bootloader_cicore/SConscript.bootloader_emucore/SConscript.firmwarecore/SConscript.kernelcore/SConscript.secmoncore/SConscript.unixInspect captured patch +376 / −128
diff --git a/core/SConscript.boardloader b/core/SConscript.boardloader
index 70d59573..59d7f17c 100644
--- a/core/SConscript.boardloader
+++ b/core/SConscript.boardloader
@@ -98,6 +98,7 @@ SOURCE_MOD += [
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
]
SOURCE_BOARDLOADER = [
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index f3a54067..32ff64dd 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -124,6 +124,7 @@ SOURCE_MOD += [
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/SConscript.bootloader_ci b/core/SConscript.bootloader_ci
index 9beb11c5..34a1f073 100644
--- a/core/SConscript.bootloader_ci
+++ b/core/SConscript.bootloader_ci
@@ -102,6 +102,7 @@ SOURCE_MOD += [
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu
index 68fdb0c8..508c18c7 100644
--- a/core/SConscript.bootloader_emu
+++ b/core/SConscript.bootloader_emu
@@ -93,6 +93,7 @@ SOURCE_MOD += [
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 79d0c9f6..257ddf85 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -268,6 +268,7 @@ SOURCE_MOD += [
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/SConscript.kernel b/core/SConscript.kernel
index 0922109d..11039609 100644
--- a/core/SConscript.kernel
+++ b/core/SConscript.kernel
@@ -222,6 +222,7 @@ SOURCE_MOD += [
'embed/util/rsod/rsod.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/SConscript.secmon b/core/SConscript.secmon
index fe64d567..2c735e2c 100644
--- a/core/SConscript.secmon
+++ b/core/SConscript.secmon
@@ -217,6 +217,7 @@ SOURCE_MOD += [
'embed/util/image/image.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 452b5ec9..a44a129c 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -243,6 +243,7 @@ SOURCE_MOD += [
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
+ 'embed/rtl/strutils.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
diff --git a/core/embed/gfx/inc/gfx/terminal.h b/core/embed/gfx/inc/gfx/terminal.h
index 51534061..066a2eff 100644
--- a/core/embed/gfx/inc/gfx/terminal.h
+++ b/core/embed/gfx/inc/gfx/terminal.h
@@ -17,14 +17,51 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef LIB_TERMINAL_H
-#define LIB_TERMINAL_H
+#pragma once
#include <gfx/gfx_color.h>
+/**
+ * Sets foreground and background colors for terminal text.
+ *
+ * Note: The current implementation does not support per-character colors.
+ * It only supports setting global foreground and background colors, which
+ * may be used before printing any text to the terminal.
+ *
+ * @param fgcolor Foreground color.
+ * @param bgcolor Background color.
+ */
void term_set_color(gfx_color_t fgcolor, gfx_color_t bgcolor);
-void term_print(const char *text, int textlen);
+
+/**
+ * Prints a text of given length to the terminal.
+ *
+ * @param text Text to print.
+ * @param textlen Number of characters to print from the text.
+ */
+void term_nprint(const char *text, int textlen);
+
+/**
+ * Prints null-terminated text to the terminal.
+ *
+ * @param text Text to print.
+ */
+void term_print(const char *text);
+
+/**
+ * Prints a 32-bit integer in decimal format to the terminal.
+ *
+ * @param value Integer value to print.
+ */
+void term_print_int32(int32_t value);
+
+/**
+ * Prints printf-style formatted text to the terminal.
+ *
+ * The function internally uses `mini_vsnprintf` to format the text.
+ *
+ * @param fmt Format string (printf-style).
+ * @param ... Additional arguments for formatting.
+ */
void term_printf(const char *fmt, ...)
__attribute__((__format__(__printf__, 1, 2)));
-
-#endif // LIB_TERMINAL_H
diff --git a/core/embed/gfx/terminal.c b/core/embed/gfx/terminal.c
index 8270348b..3dcbe401 100644
--- a/core/embed/gfx/terminal.c
+++ b/core/embed/gfx/terminal.c
@@ -24,6 +24,7 @@
#include <gfx/terminal.h>
#include <io/display.h>
#include <rtl/mini_printf.h>
+#include <rtl/strutils.h>
#include "fonts/font_bitmap.h"
@@ -148,14 +149,9 @@ static void term_redraw_rows(int start_row, int row_count) {
}
// display text using bitmap font
-void term_print(const char *text, int textlen) {
+void term_nprint(const char *text, int textlen) {
static uint8_t row = 0, col = 0;
- // determine text length if not provided
- if (textlen < 0) {
- textlen = strlen(text);
- }
-
// print characters to internal buffer (terminal_fb)
for (int i = 0; i < textlen; i++) {
switch (text[i]) {
@@ -210,16 +206,19 @@ void term_print(const char *text, int textlen) {
display_refresh();
}
-// variadic term_print
+void term_print(const char *text) { term_nprint(text, strlen(text)); }
+
+void term_print_int32(int32_t value) {
+ char buf[12] = "";
+ cstr_append_int32(buf, sizeof(buf), value);
+ term_print(buf);
+}
+
void term_printf(const char *fmt, ...) {
- if (!strchr(fmt, '%')) {
- term_print(fmt, strlen(fmt));
- } else {
- va_list va;
- va_start(va, fmt);
- char buf[256] = {0};
- int len = mini_vsnprintf(buf, sizeof(buf), fmt, va);
- term_print(buf, len);
- va_end(va);
- }
+ va_list va;
+ va_start(va, fmt);
+ char buf[256] = {0};
+ int len = mini_vsnprintf(buf, sizeof(buf), fmt, va);
+ term_nprint(buf, len);
+ va_end(va);
}
diff --git a/core/embed/projects/boardloader/sd_update.c b/core/embed/projects/boardloader/sd_update.c
index 8261b726..a002b9cd 100644
--- a/core/embed/projects/boardloader/sd_update.c
+++ b/core/embed/projects/boardloader/sd_update.c
@@ -92,43 +92,45 @@ static uint32_t check_sdcard(void) {
return 0;
}
-static void progress_callback(int pos, int len) { term_printf("."); }
+static void progress_callback(int pos, int len) { term_print("."); }
static secbool copy_sdcard(void) {
display_set_backlight(255);
- term_printf("Trezor Boardloader\n");
- term_printf("==================\n\n");
+ term_print("Trezor Boardloader\n");
+ term_print("==================\n\n");
- term_printf("bootloader found on the SD card\n\n");
- term_printf("applying bootloader in 10 seconds\n\n");
- term_printf("unplug now if you want to abort\n\n");
+ term_print("bootloader found on the SD card\n\n");
+ term_print("applying bootloader in 10 seconds\n\n");
+ term_print("unplug now if you want to abort\n\n");
uint32_t codelen;
for (int i = 10; i >= 0; i--) {
- term_printf("%d ", i);
+ term_print_int32(i);
+ term_print(" ");
+
hal_delay(1000);
codelen = check_sdcard();
if (0 == codelen) {
- term_printf("\n\nno SD card, aborting\n");
+ term_print("\n\nno SD card, aborting\n");
return secfalse;
}
}
- term_printf("\n\nerasing flash:\n\n");
+ term_print("\n\nerasing flash:\n\n");
// erase all flash (except boardloader)
if (sectrue != erase_device(progress_callback)) {
- term_printf(" failed\n");
+ term_print(" failed\n");
return secfalse;
}
- term_printf(" done\n\n");
+ term_print(" done\n\n");
ensure(flash_unlock_write(), NULL);
// copy bootloader from SD card to Flash
- term_printf("copying new bootloader from SD card\n\n");
+ term_print("copying new bootloader from SD card\n\n");
ensure(flash_area_write_data(&BOOTLOADER_AREA, 0, sdcard_buf,
IMAGE_HEADER_SIZE + codelen),
@@ -136,8 +138,8 @@ static secbool copy_sdcard(void) {
ensure(flash_lock_write(), NULL);
- term_printf("\ndone\n\n");
- term_printf("Unplug the device and remove the SD card\n");
+ term_print("\ndone\n\n");
+ term_print("Unplug the device and remove the SD card\n");
return sectrue;
}
diff --git a/core/embed/projects/bootloader/bootui.c b/core/embed/projects/bootloader/bootui.c
index 5f182c15..d27ed725 100644
--- a/core/embed/projects/bootloader/bootui.c
+++ b/core/embed/projects/bootloader/bootui.c
@@ -21,7 +21,7 @@
#include <io/display.h>
#include <io/display_utils.h>
-#include <rtl/mini_printf.h>
+#include <rtl/strutils.h>
#include "bootui.h"
#include "rust_ui_bootloader.h"
@@ -31,12 +31,18 @@
// common shared functions
-static void format_ver(const char *format, uint32_t version, char *buffer,
- size_t buffer_len) {
- mini_snprintf(buffer, buffer_len, format, (int)(version & 0xFF),
- (int)((version >> 8) & 0xFF), (int)((version >> 16) & 0xFF)
- // ignore build field (int)((version >> 24) & 0xFF)
- );
+#define VERSION_STRING_LEN 16
+
+// Formats version number encoded as uint32_t into string
+// "X.Y.Z". Buffers smaller than needed will result in truncated output.
+static void format_ver(uint32_t version, char *buffer, size_t buffer_len) {
+ buffer[0] = '\0';
+ cstr_append_int32(buffer, buffer_len, (version & 0xFF));
+ cstr_append(buffer, buffer_len, ".");
+ cstr_append_int32(buffer, buffer_len, ((version >> 8) & 0xFF));
+ cstr_append(buffer, buffer_len, ".");
+ cstr_append_int32(buffer, buffer_len, ((version >> 16) & 0xFF));
+ // ignore build field
}
// boot UI
@@ -61,10 +67,10 @@ void ui_screen_boot(const vendor_header *const vhdr,
uint32_t ui_screen_intro(const vendor_header *const vhdr,
const image_header *const hdr, bool fw_ok) {
- char bld_ver[32];
- char ver_str[64];
- format_ver("%d.%d.%d", VERSION_UINT32, bld_ver, sizeof(bld_ver));
- format_ver("%d.%d.%d", hdr->version, ver_str, sizeof(ver_str));
+ char bld_ver[VERSION_STRING_LEN];
+ char ver_str[VERSION_STRING_LEN];
+ format_ver(VERSION_UINT32, bld_ver, sizeof(bld_ver));
+ format_ver(hdr->version, ver_str, sizeof(ver_str));
return screen_intro(bld_ver, vhdr->vstr, vhdr->vstr_len, ver_str, fw_ok);
}
@@ -78,9 +84,9 @@ confirm_result_t ui_screen_install_confirm(const vendor_header *const vhdr,
secbool is_newinstall,
int version_cmp) {
uint8_t fingerprint[32];
- char ver_str[64];
+ char ver_str[VERSION_STRING_LEN];
get_image_fingerprint(hdr, fingerprint);
- format_ver("%d.%d.%d", hdr->version, ver_str, sizeof(ver_str));
+ format_ver(hdr->version, ver_str, sizeof(ver_str));
return screen_install_confirm(vhdr->vstr, vhdr->vstr_len, ver_str,
fingerprint, should_keep_seed == sectrue,
diff --git a/core/embed/projects/bootloader/wire/wire_iface_ble.c b/core/embed/projects/bootloader/wire/wire_iface_ble.c
index 6e017eea..3b41b9cd 100644
--- a/core/embed/projects/bootloader/wire/wire_iface_ble.c
+++ b/core/embed/projects/bootloader/wire/wire_iface_ble.c
@@ -24,7 +24,7 @@
#include "wire_iface_ble.h"
#include <io/ble.h>
-#include <rtl/mini_printf.h>
+#include <rtl/strutils.h>
#include <sec/rng.h>
#include <sys/sysevent.h>
#include <sys/systick.h>
@@ -171,11 +171,19 @@ bool ble_iface_start_pairing(void) {
static const char DIGITS[] = "0123456789";
static const char UPPERCASE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- char adv_name[BLE_ADV_NAME_LEN];
- mini_snprintf(adv_name, sizeof(adv_name), "%s (%c%c%c)", MODEL_FULL_NAME,
- get_random_from_charset(DIGITS),
- get_random_from_charset(UPPERCASE),
- get_random_from_charset(DIGITS));
+ char suffix[] = {
+ ' ',
+ '(',
+ get_random_from_charset(UPPERCASE),
+ get_random_from_charset(DIGITS),
+ get_random_from_charset(UPPERCASE),
+ ')',
+ '\0',
+ };
+
+ char adv_name[BLE_ADV_NAME_LEN] = "";
+ cstr_append(adv_name, sizeof(adv_name), MODEL_FULL_NAME);
+ cstr_append(adv_name, sizeof(adv_name), suffix);
if (!ble_enter_pairing_mode((const uint8_t*)adv_name,
strnlen(adv_name, BLE_ADV_NAME_LEN))) {
diff --git a/core/embed/projects/bootloader_ci/bootui.c b/core/embed/projects/bootloader_ci/bootui.c
index c66f18f7..c1905266 100644
--- a/core/embed/projects/bootloader_ci/bootui.c
+++ b/core/embed/projects/bootloader_ci/bootui.c
@@ -23,7 +23,6 @@
#include <gfx/gfx_draw.h>
#include <io/display.h>
#include <io/display_utils.h>
-#include <rtl/mini_printf.h>
#include "bootui.h"
#include "icon_done.h"
#include "icon_fail.h"
diff --git a/core/embed/projects/bootloader_ci/main.c b/core/embed/projects/bootloader_ci/main.c
index abe2e625..dd0037e3 100644
--- a/core/embed/projects/bootloader_ci/main.c
+++ b/core/embed/projects/bootloader_ci/main.c
@@ -26,7 +26,6 @@
#include <io/display.h>
#include <io/usb.h>
#include <io/usb_config.h>
-#include <rtl/mini_printf.h>
#include <sec/random_delays.h>
#include <sec/rng.h>
#include <sys/bootargs.h>
diff --git a/core/embed/rtl/error_handling.c b/core/embed/rtl/error_handling.c
index 89b05302..5fad0de1 100644
--- a/core/embed/rtl/error_handling.c
+++ b/core/embed/rtl/error_handling.c
@@ -19,7 +19,6 @@
#include <trezor_rtl.h>
-#include <rtl/mini_printf.h>
#include <sys/bootutils.h>
#include <sys/system.h>
@@ -71,11 +70,9 @@ void __attribute__((noreturn)) show_wipe_code_screen(void) {
const char *title = "Wipe code entered";
- mini_snprintf(info.title, sizeof(info.title), "%s", title);
- mini_snprintf(info.message, sizeof(info.message), "%s",
- ALL_DATA_ERASED_MESSAGE);
- mini_snprintf(info.footer, sizeof(info.footer), "%s",
- RECONNECT_DEVICE_MESSAGE);
+ strncpy(info.title, title, sizeof(info.title) - 1);
+ strncpy(info.message, ALL_DATA_ERASED_MESSAGE, sizeof(info.message) - 1);
+ strncpy(info.footer, RECONNECT_DEVICE_MESSAGE, sizeof(info.footer) - 1);
reboot_and_wipe(&info);
@@ -108,11 +105,9 @@ void __attribute__((noreturn)) show_pin_too_many_screen(void) {
const char *title = "Pin attempts exceeded";
- mini_snprintf(info.title, sizeof(info.title), "%s", title);
- mini_snprintf(info.message, sizeof(info.message), "%s",
- ALL_DATA_ERASED_MESSAGE);
- mini_snprintf(info.footer, sizeof(info.footer), "%s",
- RECONNECT_DEVICE_MESSAGE);
+ strncpy(info.title, title, sizeof(info.title) - 1);
+ strncpy(info.message, ALL_DATA_ERASED_MESSAGE, sizeof(info.message) - 1);
+ strncpy(info.footer, RECONNECT_DEVICE_MESSAGE, sizeof(info.footer) - 1);
reboot_and_wipe(&info);
while (1)
diff --git a/core/embed/rtl/inc/rtl/strutils.h b/core/embed/rtl/inc/rtl/strutils.h
index e4e82c33..ed4fae3f 100644
--- a/core/embed/rtl/inc/rtl/strutils.h
+++ b/core/embed/rtl/inc/rtl/strutils.h
@@ -21,45 +21,146 @@
#include <trezor_types.h>
-// Parses the string as a signed 32-bit integer in the specified base.
-//
-// If the entire string represents a valid integer, the parsed value is stored
-// in 'result', and the function returns true. Otherwise, the function returns
-// false, and 'result' remains unchanged.
+/**
+ * Parses the string as a signed 32-bit integer in the specified base.
+ *
+ * If the entire string represents a valid integer, the parsed value is stored
+ * in 'result', and the function returns true. Otherwise, the function returns
+ * false, and 'result' remains unchanged.
+ *
+ * @param str The null-terminated C-string to parse
+ * @param base The numeral base (e.g., 10 for decimal, 16 for hexadecimal)
+ * @param result Pointer to store the parsed integer value
+ * @return true if parsing was successful, false otherwise
+ */
bool cstr_parse_int32(const char* str, int base, int32_t* result);
-// Parses the string as a unsigned 32-bit integer in the specified base.
-//
-// If the entire string represents a valid integer, the parsed value is stored
-// in 'result', and the function returns true. Otherwise, the function returns
-// false, and 'result' remains unchanged.
+/**
+ * Parses the string as a unsigned 32-bit integer in the specified base.
+ *
+ * If the entire string represents a valid integer, the parsed value is stored
+ * in 'result', and the function returns true. Otherwise, the function returns
+ * false, and 'result' remains unchanged.
+ *
+ * @param str The null-terminated C-string to parse
+ * @param base The numeral base (e.g., 10 for decimal, 16 for hexadecimal)
+ * @param result Pointer to store the parsed integer value
+ * @return true if parsing was successful, false otherwise
+ */
bool cstr_parse_uint32(const char* str, int base, uint32_t* result);
-// Skips leading whitespace in the string and returns the pointer to the first
-// non-whitespace character.
+/**
+ * Skips leading whitespace in the string and returns the pointer to the first
+ * non-whitespace character.
+ *
+ * @param str The null-terminated C-string to process
+ * @return Pointer to the first non-whitespace character in the string
+ */
const char* cstr_skip_whitespace(const char* str);
-// Returns true if the null-terminated C-string starts with the prefix
+/**
+ * Returns true if the null-terminated C-string starts with the prefix.
+ *
+ * @param str The null-terminated C-string to check
+ * @param prefix The prefix to look for
+ * @return true if the string starts with the prefix, false otherwise
+ */
bool cstr_starts_with(const char* str, const char* prefix);
-// Decodes the string as a hexadecimal string and writes the binary data to the
-// destination buffer.
-//
-// Hexadecimal digits can be in upper or lower case and may be separated by
-// whitespace.
-//
-// Number of bytes written to the destination buffer is stored in
-// `bytes_written` (even if the function returns false).
-//
-// Returns true if the entire slice was parsed successfully.
+/**
+ * Decodes the string as a hexadecimal string and writes the binary data to the
+ * destination buffer.
+ *
+ * Hexadecimal digits can be in upper or lower case and may be separated by
+ * whitespace.
+ *
+ * @param str The null-terminated C-string to decode
+ * @param dst Pointer to the destination buffer to write the binary data
+ * @param dst_len Length of the destination buffer in bytes
+ * @param bytes_written Pointer to store the number of bytes written to the
+ * destination buffer
+ *
+ * @return true if the entire string was parsed successfully, false otherwise
+ */
bool cstr_decode_hex(const char* str, uint8_t* dst, size_t dst_len,
size_t* bytes_written);
-// Encodes binary data to null-terminated hexadecimal string
-//
-// Destination buffer must be at least 2 * src_len + 1 bytes long.
-//
-// If the destination buffer is too small, the function will return false and
-// the destination buffer will be set to empty string.
+/**
+ * Encodes binary data to null-terminated hexadecimal string
+ *
+ * Destination buffer must be at least 2 * src_len + 1 bytes long otherwise
+ * the function will return false and the destination buffer will be set to
+ * empty string.
+ *
+ * @param dst Pointer to the destination buffer to write the hexadecimal string
+ * @param dst_len Length of the destination buffer in bytes
+ * @param src Pointer to the source binary data
+ * @param src_len Length of the source binary data in bytes
+ * @return true if encoding was successful, false otherwise
+ */
bool cstr_encode_hex(char* dst, size_t dst_len, const void* src,
size_t src_len);
+
+/**
+ * Appends the null-terminated C-string 'src' to the end of the null-terminated
+ * C-string 'dst', ensuring that the total length does not exceed 'dst_len'.
+ *
+ * The function ensures that 'dst' remains null-terminated after the operation.
+ * If there is not enough space in 'dst' to append 'src', the function returns
+ * appends as much of 'src' as possible and returns false.
+ *
+ * @param dst Pointer to the destination C-string
+ * @param dst_len Length of the destination buffer in bytes
+ * @param src Pointer to the source C-string to append
+ * @return true if the entire string was appended successfully, false otherwise
+ */
+bool cstr_append(char* dst, size_t dst_len, const char* src);
+
+/**
+ * Appends the string representation of a signed 32-bit integer to the end of
+ * the null-terminated C-string 'dst', ensuring that the total length does not
+ * exceed 'dst_len'.
+ *
+ * The function ensures that 'dst' remains null-terminated after the operation.
+ * If there is not enough space in 'dst' to append the integer, the function
+ * returns false, but appends as much as possible.
+ *
+ * @param dst Pointer to the destination C-string
+ * @param dst_len Length of the destination buffer in bytes
+ * @param value The signed 32-bit integer to append
+ * @return true if the entire integer was appended successfully, false otherwise
+ */
+bool cstr_append_int32(char* dst, size_t dst_len, int32_t value);
+
+/**
+ * Appends the string representation of an unsigned 32-bit integer to the end of
+ * the null-terminated C-string 'dst', ensuring that the total length does not
+ * exceed 'dst_len'.
+ *
+ * The function ensures that 'dst' remains null-terminated after the operation.
+ * If there is not enough space in 'dst' to append the integer, the function
+ * returns false, but appends as much as possible.
+ *
+ * @param dst Pointer to the destination C-string
+ * @param dst_len Length of the destination buffer in bytes
+ * @param value The unsigned 32-bit integer to append
+ * @return true if the entire integer was appended successfully, false otherwise
+ */
+bool cstr_append_uint32(char* dst, size_t dst_len, uint32_t value);
+
+/**
+ * Appends the hexadecimal string representation of an unsigned 32-bit
+ * integer to the end of the null-terminated C-string 'dst', ensuring that
+ * the total length does not exceed 'dst_len'.
+ *
+ * The function ensures that 'dst' remains null-terminated after the operation.
+ * If there is not enough space in 'dst' to append the hexadecimal string, the
+ * function returns false, but appends as much as possible.
+ *
+ * @param dst Pointer to the destination C-string
+ * @param dst_len Length of the destination buffer in bytes
+ * @param value The unsigned 32-bit integer to append in hexadecimal format
+ * @return true if the entire hexadecimal string was appended successfully,
+ * false otherwise
+ */
+bool cstr_append_uint32_hex(char* dst, size_t dst_len, uint32_t value);
diff --git a/core/embed/rtl/strutils.c b/core/embed/rtl/strutils.c
index a3863f1d..4036c7e3 100644
--- a/core/embed/rtl/strutils.c
+++ b/core/embed/rtl/strutils.c
@@ -93,10 +93,10 @@ bool cstr_decode_hex(const char* str, uint8_t* dst, size_t dst_len,
return *cstr_skip_whitespace(str) == '\0';
}
+static const char hex_chars[] = "0123456789ABCDEF";
+
bool cstr_encode_hex(char* dst, size_t dst_len, const void* src,
size_t src_len) {
- static const char hex[] = "0123456789ABCDEF";
-
if (dst_len < src_len * 2 + 1) {
if (dst_len > 0) {
dst[0] = '\0';
@@ -105,10 +105,81 @@ bool cstr_encode_hex(char* dst, size_t dst_len, const void* src,
}
for (size_t i = 0; i < src_len; i++) {
- dst[i * 2] = hex[((uint8_t*)src)[i] >> 4];
- dst[i * 2 + 1] = hex[((uint8_t*)src)[i] & 0x0F];
+ dst[i * 2] = hex_chars[((uint8_t*)src)[i] >> 4];
+ dst[i * 2 + 1] = hex_chars[((uint8_t*)src)[i] & 0x0F];
}
dst[src_len * 2] = '\0';
return true;
}
+
+bool cstr_append(char* dst, size_t dst_len, const char* src) {
+ while (*dst != '\0' && dst_len > 1) {
+ dst++;
+ dst_len--;
+ }
+
+ while (*src != '\0' && dst_len > 1) {
+ *dst++ = *src++;
+ dst_len--;
+ }
+
+ if (dst_len > 0) {
+ *dst = '\0';
+ }
+
+ return *src == '\0';
+}
+
+bool cstr_append_uint32(char* dst, size_t dst_len, uint32_t value) {
+ char buffer[12] = "";
+ char* p = buffer + sizeof(buffer) - 1;
+
+ *p = '\0';
+
+ if (value == 0) {
+ *(--p) = '0';
+ } else {
+ while (value > 0) {
+ *(--p) = (char)('0' + value % 10);
+ value /= 10;
+ }
+ }
+
+ return cstr_append(dst, dst_len, p);
+}
+
+bool cstr_append_int32(char* dst, size_t dst_len, int32_t value) {
+ char buffer[12] = "";
+ char* p = buffer + sizeof(buffer) - 1;
+
+ *p = '\0';
+
+ if (value == 0) {
+ *(--p) = '0';
+ } else {
+ bool negative = value < 0;
+ uint32_t abs_value = negative ? -value : value;
+
+ while (abs_value > 0) {
+ *(--p) = (char)('0' + abs_value % 10);
+ abs_value /= 10;
+ }
+
+ if (negative) {
+ *(--p) = '-';
+ }
+ }
+
+ return cstr_append(dst, dst_len, p);
+}
+
+bool cstr_append_uint32_hex(char* dst, size_t dst_len, uint32_t value) {
+ char temp[sizeof(value) * 2 + 1];
+ for (int i = 2 * sizeof(value) - 1; i >= 0; i--) {
+ temp[i] = hex_chars[value & 0x0F];
+ value >>= 4;
+ }
+ temp[sizeof(temp) - 1] = '\0';
+ return cstr_append(dst, dst_len, temp);
+}
diff --git a/core/embed/sys/task/inc/sys/systask.h b/core/embed/sys/task/inc/sys/systask.h
index 85f71b2a..1eecc6e7 100644
--- a/core/embed/sys/task/inc/sys/systask.h
+++ b/core/embed/sys/task/inc/sys/systask.h
@@ -80,7 +80,7 @@ typedef struct {
// Arguments passed to `systask_exit_fatal()`
struct {
- uint32_t line;
+ int32_t line;
char file[64];
char expr[64];
} fatal;
diff --git a/core/embed/sys/task/stm32/system.c b/core/embed/sys/task/stm32/system.c
index 7a27ba57..8b88a114 100644
--- a/core/embed/sys/task/stm32/system.c
+++ b/core/embed/sys/task/stm32/system.c
@@ -20,7 +20,7 @@
#include <trezor_bsp.h>
#include <trezor_rtl.h>
-#include <rtl/mini_printf.h>
+#include <rtl/strutils.h>
#include <sys/bootargs.h>
#include <sys/bootutils.h>
#include <sys/linker_utils.h>
@@ -177,7 +177,6 @@ __attribute((naked, noreturn, no_stack_protector)) void system_emergency_rescue(
#ifdef STM32U5
const char* system_fault_message(const system_fault_t* fault) {
- static char message[48] = {0};
const char* fault_type = "FAULT";
switch (fault->irqn) {
case HardFault_IRQn:
@@ -202,13 +201,16 @@ const char* system_fault_message(const system_fault_t* fault) {
fault_type = "CS";
break;
}
- mini_snprintf(message, sizeof(message), "%s @ 0x%08X", fault_type,
- (unsigned int)fault->pc);
+
+ static char message[48] = "";
+ cstr_append(message, sizeof(message), fault_type);
+ cstr_append(message, sizeof(message), " @ 0x");
+ cstr_append_uint32_hex(message, sizeof(message), fault->pc);
+
return message;
}
#else // STM32U5
const char* system_fault_message(const system_fault_t* fault) {
- static char message[48] = {0};
const char* fault_type = "FAULT";
switch (fault->irqn) {
case HardFault_IRQn:
@@ -227,8 +229,12 @@ const char* system_fault_message(const system_fault_t* fault) {
fault_type = "CS";
break;
}
- mini_snprintf(message, sizeof(message), "%s @ 0x%08X", fault_type,
- (unsigned int)fault->pc);
+
+ static char message[48] = "";
+ cstr_append(message, sizeof(message), fault_type);
+ cstr_append(message, sizeof(message), " @ 0x");
+ cstr_append_uint32_hex(message, sizeof(message), fault->pc);
+
return message;
}
#endif // STM32U5
diff --git a/core/embed/util/rsod/rsod.c b/core/embed/util/rsod/rsod.c
index 8fdfde28..b0721562 100644
--- a/core/embed/util/rsod/rsod.c
+++ b/core/embed/util/rsod/rsod.c
@@ -17,13 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <trezor_rtl.h>
+
#include <gfx/terminal.h>
#include <io/display.h>
-#include <rtl/mini_printf.h>
#include <sys/bootutils.h>
#include <sys/system.h>
#include <util/rsod.h>
+#include <rtl/strutils.h>
+
#ifdef SCM_REVISION_INIT
#include <util/scm_revision.h>
#endif
@@ -31,7 +34,7 @@
#define RSOD_DEFAULT_TITLE "Internal error";
#define RSOD_DEFAULT_MESSAGE "Unspecified";
#define RSOD_DEFAULT_FOOTER "Please visit trezor.io/rsod";
-#define RSOD_EXIT_MESSAGE "Exit %d"
+#define RSOD_EXIT_MESSAGE "Exit " // followed by exit code
#ifdef KERNEL_MODE
@@ -51,13 +54,13 @@ void rsod_terminal(const systask_postmortem_t* pminfo) {
const char* message = RSOD_DEFAULT_MESSAGE;
const char* footer = RSOD_DEFAULT_FOOTER;
const char* file = NULL;
- char message_buf[32] = {0};
+ char message_buf[32] = "";
int line = 0;
switch (pminfo->reason) {
case TASK_TERM_REASON_EXIT:
- mini_snprintf(message_buf, sizeof(message_buf), RSOD_EXIT_MESSAGE,
- pminfo->exit.code);
+ cstr_append(message_buf, sizeof(message_buf), RSOD_EXIT_MESSAGE);
+ cstr_append_int32(message_buf, sizeof(message_buf), pminfo->exit.code);
message = message_buf;
break;
case TASK_TERM_REASON_ERROR:
@@ -82,25 +85,35 @@ void rsod_terminal(const systask_postmortem_t* pminfo) {
}
if (title != NULL) {
- term_printf("%s\n", title);
+ term_print(title);
+ term_print("\n");
}
if (message != NULL) {
- term_printf("msg : %s\n", message);
+ term_print("msg : ");
+ term_print(message);
+ term_print("\n");
}
if (file) {
- term_printf("file: %s:%d\n", file, line);
+ term_print("file: ");
+ term_print(file);
+ term_print(":");
+ term_print_int32(line);
+ term_print("\n");
}
#ifdef SCM_REVISION_INIT
- const uint8_t* rev = SCM_REVISION;
- term_printf("rev : %02x%02x%02x%02x%02x\n", rev[0], rev[1], rev[2], rev[3],
- rev[4]);
+ char rev[10 + 1];
+ cstr_encode_hex(rev, sizeof(rev), SCM_REVISION, (sizeof(rev) - 1) / 2);
+ term_print("rev : ");
+ term_print(rev);
#endif
if (footer != NULL) {
- term_printf("\n%s\n", footer);
+ term_print("\n");
+ term_print(footer);
+ term_print("\n");
}
display_set_backlight(255);
@@ -116,12 +129,12 @@ void rsod_gui(const systask_postmortem_t* pminfo) {
const char* title = RSOD_DEFAULT_TITLE;
const char* message = RSOD_DEFAULT_MESSAGE;
const char* footer = RSOD_DEFAULT_FOOTER;
- char message_buf[128] = {0};
+ char message_buf[128] = "";
switch (pminfo->reason) {
case TASK_TERM_REASON_EXIT:
- mini_snprintf(message_buf, sizeof(message_buf), RSOD_EXIT_MESSAGE,
- pminfo->exit.code);
+ cstr_append(message_buf, sizeof(message_buf), RSOD_EXIT_MESSAGE);
+ cstr_append_int32(message_buf, sizeof(message_buf), pminfo->exit.code);
message = message_buf;
break;
@@ -140,11 +153,15 @@ void rsod_gui(const systask_postmortem_t* pminfo) {
case TASK_TERM_REASON_FATAL:
message = pminfo->fatal.expr;
if (message[0] == '\0') {
- mini_snprintf(message_buf, sizeof(message_buf), "%s:%u",
- pminfo->fatal.file, (unsigned int)pminfo->fatal.line);
+ cstr_append(message_buf, sizeof(message_buf), pminfo->fatal.file);
+ cstr_append(message_buf, sizeof(message_buf), ":");
+ cstr_append_int32(message_buf, sizeof(message_buf), pminfo->fatal.line);
} else {
- mini_snprintf(message_buf, sizeof(message_buf), "%s\n%s:%u", message,
- pminfo->fatal.file, (unsigned int)pminfo->fatal.line);
+ cstr_append(message_buf, sizeof(message_buf), message);
+ cstr_append(message_buf, sizeof(message_buf), "\n");
+ cstr_append(message_buf, sizeof(message_buf), pminfo->fatal.file);
+ cstr_append(message_buf, sizeof(message_buf), ":");
+ cstr_append_int32(message_buf, sizeof(message_buf), pminfo->fatal.line);
}
message = message_buf;
break;
Why this scored 34/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.