refactor(core): assorted micropython-1.28.0 fixes
What changed, and why it matters
This commit updates Trezor's embedded MicroPython interpreter from an older version to 1.28.0. It pulls in several upstream MicroPython bug fixes, including stricter buffer-size checks for converting integers to bytes, a new stack-safety API, preventing Ctrl+C from interrupting frozen boot code, fixing a stdout write return value, and blocking accidental creation of bytearrays from plain strings without an encoding. The changes are mostly defensive hardening and compatibility fixes rather than a single known exploit patch. There is no vendor statement that this is a security fix, no CVE, and no credited researcher.
Treat this as a routine but worthwhile maintenance/hardening update. Review the referenced upstream MicroPython commits for any additional security implications, run the firmware test suite, and include this update in normal release notes. No urgent security response is indicated by the available evidence.
Security signals we found
Synchronizes upstream MicroPython fixes that include buffer-size and stack-safety hardening
Prevents Ctrl+C interruption of frozen boot code, reducing denial-of-service/control-flow risk during boot
Fixes sys.stdout.buffer.write() return value, which could affect code relying on correct I/O semantics
Blocks bytearray(str) without encoding, preventing ambiguous/buggy string-to-bytes conversions
No explicit security framing, CVE, or researcher attribution in commit or references
Evidence from the diff
The diff is a synchronization commit that adapts Trezor firmware to MicroPython 1.28.0. Notable upstream fixes referenced are: (1) int.to_bytes() buffer size checks; (2) new cstack API for stack checking with a limit margin; (3) disabling Ctrl+C interruption of frozen boot code; (4) correct return value for sys.stdout.buffer.write(); and (5) rejecting bytearray(str) without an encoding. The Trezor-specific changes add cstack.c and objcode.c to the build, update module declarations to const, adjust mp_hal_stdout_tx_strn to return bytes written, update helper macros from MP_OBJ_IS_SMALL_INT/MP_OBJ_IS_TYPE to mp_obj_is_small_int/mp_obj_is_exact_type, and update mpz_as_bytes call signature. A test is updated to use byte literals instead of bytearray(str).
Changed components
core/SConscript.firmwarecore/SConscript.unixcore/embed/projects/firmware/main.ccore/embed/projects/firmware/mpconfigport.hcore/embed/projects/firmware/mphalport.ccore/embed/projects/unix/mpconfigport.hcore/embed/rust/librust.hcore/embed/upymod/build.rscore/embed/upymod/modtrezorcrypto/modtrezorcrypto-monero.hcore/embed/upymod/modtrezorutils/modtrezorutils.ccore/embed/upymod/trezorobj.hcore/tests/test_apps.nem.transfer.pyvendor/micropython (upstream submodule)Inspect captured patch +36 / −40
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index b817b019..00b62db7 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -349,6 +349,7 @@ SOURCE_MICROPYTHON = [
'vendor/micropython/py/builtinhelp.c',
'vendor/micropython/py/builtinimport.c',
'vendor/micropython/py/compile.c',
+ 'vendor/micropython/py/cstack.c',
'vendor/micropython/py/emitbc.c',
'vendor/micropython/py/emitcommon.c',
'vendor/micropython/py/emitglue.c',
@@ -377,6 +378,7 @@ SOURCE_MICROPYTHON = [
'vendor/micropython/py/objboundmeth.c',
'vendor/micropython/py/objcell.c',
'vendor/micropython/py/objclosure.c',
+ 'vendor/micropython/py/objcode.c',
'vendor/micropython/py/objcomplex.c',
'vendor/micropython/py/objdict.c',
'vendor/micropython/py/objenumerate.c',
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 9a0a8298..34e4597c 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -328,6 +328,7 @@ SOURCE_MICROPYTHON = [
'vendor/micropython/py/builtinhelp.c',
'vendor/micropython/py/builtinimport.c',
'vendor/micropython/py/compile.c',
+ 'vendor/micropython/py/cstack.c',
'vendor/micropython/py/emitbc.c',
'vendor/micropython/py/emitcommon.c',
'vendor/micropython/py/emitglue.c',
@@ -374,6 +375,7 @@ SOURCE_MICROPYTHON = [
'vendor/micropython/py/objbool.c',
'vendor/micropython/py/objboundmeth.c',
'vendor/micropython/py/objcell.c',
+ 'vendor/micropython/py/objcode.c',
'vendor/micropython/py/objclosure.c',
'vendor/micropython/py/objcomplex.c',
'vendor/micropython/py/objdeque.c',
diff --git a/core/embed/projects/firmware/main.c b/core/embed/projects/firmware/main.c
index 01b59baf..ca814a32 100644
--- a/core/embed/projects/firmware/main.c
+++ b/core/embed/projects/firmware/main.c
@@ -153,7 +153,7 @@ int main_func(uint32_t cmd, void *arg) {
// Execute the main script
LOG_INF("Executing main script");
- pyexec_frozen_module("main.py");
+ pyexec_frozen_module("main.py", false);
// Clean up
LOG_INF("Main script finished, cleaning up");
diff --git a/core/embed/projects/firmware/mpconfigport.h b/core/embed/projects/firmware/mpconfigport.h
index f5c710ae..7f40150f 100644
--- a/core/embed/projects/firmware/mpconfigport.h
+++ b/core/embed/projects/firmware/mpconfigport.h
@@ -82,7 +82,6 @@
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#define MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND (1)
#define MICROPY_STREAMS_NON_BLOCK (1)
-#define MICROPY_MODULE_WEAK_LINKS (0)
#define MICROPY_CAN_OVERRIDE_BUILTINS (0)
#define MICROPY_USE_INTERNAL_ERRNO (1)
#define MICROPY_ENABLE_SCHEDULER (0)
diff --git a/core/embed/projects/firmware/mphalport.c b/core/embed/projects/firmware/mphalport.c
index 0b138cde..2dbd063f 100644
--- a/core/embed/projects/firmware/mphalport.c
+++ b/core/embed/projects/firmware/mphalport.c
@@ -37,9 +37,12 @@ int mp_hal_stdin_rx_chr(void) {
#endif
}
-void mp_hal_stdout_tx_strn(const char *str, size_t len) {
+mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
#ifdef USE_DBG_CONSOLE
- dbg_console_write(str, len);
+ ssize_t written = dbg_console_write(str, len);
+ return (written > 0 ? written : 0);
+#else
+ return 0;
#endif
}
diff --git a/core/embed/projects/unix/mpconfigport.h b/core/embed/projects/unix/mpconfigport.h
index 3852e03a..1692cd08 100644
--- a/core/embed/projects/unix/mpconfigport.h
+++ b/core/embed/projects/unix/mpconfigport.h
@@ -90,7 +90,6 @@
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#define MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND (1)
#define MICROPY_STREAMS_NON_BLOCK (1)
-#define MICROPY_MODULE_WEAK_LINKS (0)
#define MICROPY_CAN_OVERRIDE_BUILTINS (0)
#define MICROPY_VFS_POSIX (1)
#define MICROPY_USE_INTERNAL_ERRNO (0)
diff --git a/core/embed/rust/librust.h b/core/embed/rust/librust.h
index fb46c75a..706a2c06 100644
--- a/core/embed/rust/librust.h
+++ b/core/embed/rust/librust.h
@@ -7,21 +7,21 @@ mp_obj_t protobuf_debug_msg_type();
mp_obj_t protobuf_debug_msg_def_type();
#endif
-extern mp_obj_module_t mp_module_trezorproto;
-extern mp_obj_module_t mp_module_trezorui_api;
-extern mp_obj_module_t mp_module_trezortranslate;
-extern mp_obj_module_t mp_module_trezorble;
-extern mp_obj_module_t mp_module_trezorthp;
+extern const mp_obj_module_t mp_module_trezorproto;
+extern const mp_obj_module_t mp_module_trezorui_api;
+extern const mp_obj_module_t mp_module_trezortranslate;
+extern const mp_obj_module_t mp_module_trezorble;
+extern const mp_obj_module_t mp_module_trezorthp;
#ifdef USE_DBG_CONSOLE
-extern mp_obj_module_t mp_module_trezorlog;
+extern const mp_obj_module_t mp_module_trezorlog;
#endif
#if !PYOPT
mp_obj_t ui_debug_layout_type();
#ifdef TREZOR_EMULATOR
-extern mp_obj_module_t mp_module_coveragedata;
+extern const mp_obj_module_t mp_module_coveragedata;
#endif
#endif
diff --git a/core/embed/upymod/build.rs b/core/embed/upymod/build.rs
index 1077c156..b8564174 100644
--- a/core/embed/upymod/build.rs
+++ b/core/embed/upymod/build.rs
@@ -18,7 +18,8 @@ fn main() -> Result<()> {
if cfg!(feature = "emulator") {
// There are two mpconfigport.h files in both ports/unix and projects/unix.
- // The first one has precedence and is used for compilation.
+ // The first one has precedence and is used for compilation. We need mphalport.h
+ // from the other.
lib.add_include("../projects/unix");
lib.add_include(PathBuf::from(mpy_dir).join("ports/unix"));
} else if cfg!(feature = "mcu_stm32") {
@@ -118,20 +119,11 @@ fn main() -> Result<()> {
lib.add_sources_in_dir_with_attrs(mpy_dir, ["py/gc.c", "py/pystack.c", "py/vm.c"], attrs);
- // silence warning about unterminated string literals
- // TODO: remove this after we upgrade MicroPython
- let attrs_silence_unterminated =
- xbuild::CompileAttrs::new().with_flag("-Wno-unterminated-string-initialization");
- lib.add_sources_in_dir_with_attrs(
- mpy_dir,
- ["extmod/moductypes.c"],
- Some(attrs_silence_unterminated),
- );
-
lib.add_sources_in_dir(
mpy_dir,
[
"extmod/modtime.c",
+ "extmod/moductypes.c",
"shared/timeutils/timeutils.c",
"py/argcheck.c",
"py/asmarm.c",
@@ -146,6 +138,7 @@ fn main() -> Result<()> {
"py/builtinhelp.c",
"py/builtinimport.c",
"py/compile.c",
+ "py/cstack.c",
"py/emitbc.c",
"py/emitcommon.c",
"py/emitglue.c",
@@ -164,8 +157,6 @@ fn main() -> Result<()> {
"py/modmicropython.c",
"py/modstruct.c",
"py/modsys.c",
- "py/modthread.c",
- "py/moderrno.c",
"py/mpprint.c",
"py/mpstate.c",
"py/mpz.c",
@@ -177,6 +168,7 @@ fn main() -> Result<()> {
"py/objboundmeth.c",
"py/objcell.c",
"py/objclosure.c",
+ "py/objcode.c",
"py/objcomplex.c",
"py/objdeque.c",
"py/objdict.c",
@@ -232,8 +224,6 @@ fn main() -> Result<()> {
);
if cfg!(feature = "emulator") {
- lib.add_defines([("MP_CONFIGFILE", Some("\"mpconfigport.h\""))]);
-
if cfg!(feature = "frozen") {
lib.add_define("TREZOR_EMULATOR_FROZEN", None);
}
diff --git a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-monero.h b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-monero.h
index 10438e31..a67614b3 100644
--- a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-monero.h
+++ b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-monero.h
@@ -51,7 +51,6 @@ typedef struct _mp_obj_bignum256modm_t {
static const mp_obj_type_t mod_trezorcrypto_monero_ge25519_type;
static const mp_obj_type_t mod_trezorcrypto_monero_bignum256modm_type;
-STATIC const mp_obj_type_t mod_trezorcrypto_monero_hasher_type;
#define MP_OBJ_IS_GE25519(o) \
MP_OBJ_IS_TYPE((o), &mod_trezorcrypto_monero_ge25519_type)
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index 42d8ba08..50300ce6 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -23,6 +23,7 @@
#if MICROPY_OOM_CALLBACK
#include <py/gc.h>
#endif
+#include "py/objmodule.h"
#include "py/objstr.h"
#include "py/runtime.h"
@@ -445,7 +446,7 @@ static MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_estimate_unused_stack_obj,
#if MICROPY_OOM_CALLBACK
static void gc_oom_callback(void) {
- gc_dump_info();
+ gc_dump_info(&mp_plat_print);
#if BLOCK_ON_VCP
dump_meminfo_json(NULL); // dump to stdout
#endif
@@ -517,7 +518,7 @@ static mp_obj_t mod_trezorutils_update_gc_info() {
// pointers (resulting in `gc_mark_subtree` false-positives).
#ifdef TREZOR_EMULATOR
if (prev_free > current_gc_info.free) {
- gc_dump_info();
+ gc_dump_info(&mp_plat_print);
mp_raise_msg(&mp_type_AssertionError,
MP_ERROR_TEXT("Free heap size decreased"));
}
diff --git a/core/embed/upymod/trezorobj.h b/core/embed/upymod/trezorobj.h
index 825f229d..835d2370 100644
--- a/core/embed/upymod/trezorobj.h
+++ b/core/embed/upymod/trezorobj.h
@@ -30,10 +30,10 @@
// Casts int object into mp_int_t, without any conversions. Raises if object is
// not int or if it does not fit into mp_int_t representation.
static inline mp_int_t trezor_obj_get_int(mp_obj_t obj) {
- if (MP_OBJ_IS_SMALL_INT(obj)) {
+ if (mp_obj_is_small_int(obj)) {
mp_int_t i = MP_OBJ_SMALL_INT_VALUE(obj);
return i;
- } else if (MP_OBJ_IS_TYPE(obj, &mp_type_int)) {
+ } else if (mp_obj_is_exact_type(obj, &mp_type_int)) {
mp_int_t i = 0;
mp_obj_int_t *self = MP_OBJ_TO_PTR(obj);
if (!mpz_as_int_checked(&self->mpz, &i)) {
@@ -50,14 +50,14 @@ static inline mp_int_t trezor_obj_get_int(mp_obj_t obj) {
// not int or if it does not fit into mp_uint_t representation (or is less than
// 0).
static inline mp_uint_t trezor_obj_get_uint(mp_obj_t obj) {
- if (MP_OBJ_IS_SMALL_INT(obj)) {
+ if (mp_obj_is_small_int(obj)) {
mp_int_t i = MP_OBJ_SMALL_INT_VALUE(obj);
if (i < 0) {
mp_raise_TypeError(MP_ERROR_TEXT("value is negative"));
}
mp_uint_t u = i;
return u;
- } else if (MP_OBJ_IS_TYPE(obj, &mp_type_int)) {
+ } else if (mp_obj_is_exact_type(obj, &mp_type_int)) {
mp_uint_t u = 0;
mp_obj_int_t *self = MP_OBJ_TO_PTR(obj);
if (!mpz_as_uint_checked(&self->mpz, &u)) {
@@ -89,20 +89,21 @@ static inline uint16_t trezor_obj_get_uint16(mp_obj_t obj) {
}
static inline uint64_t trezor_obj_get_uint64(mp_const_obj_t obj) {
- if (MP_OBJ_IS_SMALL_INT(obj)) {
+ if (mp_obj_is_small_int(obj)) {
mp_int_t i = MP_OBJ_SMALL_INT_VALUE(obj);
if (i < 0) {
mp_raise_TypeError(MP_ERROR_TEXT("value is negative"));
}
mp_uint_t u = i;
return u;
- } else if (MP_OBJ_IS_TYPE(obj, &mp_type_int)) {
+ } else if (mp_obj_is_exact_type(obj, &mp_type_int)) {
uint64_t u = 0;
mp_obj_int_t *self = MP_OBJ_TO_PTR(obj);
if (self->mpz.neg != 0) {
mp_raise_TypeError(MP_ERROR_TEXT("value is negative"));
}
- mpz_as_bytes(&self->mpz, MP_ENDIANNESS_BIG, sizeof(uint64_t), (byte *)&u);
+ mpz_as_bytes(&self->mpz, MP_ENDIANNESS_BIG, /*as_signed=*/false,
+ sizeof(uint64_t), (byte *)&u);
return u;
} else {
mp_raise_TypeError(MP_ERROR_TEXT("value is not int"));
diff --git a/core/tests/test_apps.nem.transfer.py b/core/tests/test_apps.nem.transfer.py
index 478dc304..1db7ece9 100644
--- a/core/tests/test_apps.nem.transfer.py
+++ b/core/tests/test_apps.nem.transfer.py
@@ -66,7 +66,7 @@ class TestNemTransfer(unittest.TestCase):
bytes.fromhex(
"8d07f90fb4bbe7715fa327c926770166a11be2e494a970605f2e12557f66c9b9"
),
- bytearray("Good luck!"),
+ b"Good luck!",
False,
)
self.assertEqual(
@@ -125,7 +125,7 @@ class TestNemTransfer(unittest.TestCase):
bytes.fromhex(
"994793ba1c789fa9bdea918afc9b06e2d0309beb1081ac5b6952991e4defd324"
),
- bytearray("sending you 3 pairs of paddles\n"),
+ b"sending you 3 pairs of paddles\n",
False,
)
@@ -163,7 +163,7 @@ class TestNemTransfer(unittest.TestCase):
bytes.fromhex(
"f85ab43dad059b9d2331ddacc384ad925d3467f03207182e01296bacfb242d01"
),
- bytearray("enjoy! :)"),
+ b"enjoy! :)",
False,
)
serialize_mosaic(t, "imre.g", "tokens", 1)
Why this scored 22/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.