refactor(core/rust): allow defining new micropython exceptions
What changed, and why it matters
This commit is a code cleanup in the Trezor firmware's Rust layer. It introduces a dedicated helper module for creating MicroPython exceptions and refactors existing error handling to use those helpers. There is no direct evidence in the commit that it fixes a security vulnerability; it appears to be a maintainability refactor that also enables future custom exception types.
No immediate security action required. Treat as routine refactoring. If auditing, verify that the new `new_exception*` wrappers preserve the previous behavior (e.g., argument count and exception type mapping) and that any future custom exceptions defined with `define_exception` are not used to bypass error-handling semantics.
Security signals we found
Refactor only: no vulnerability pattern is patched
No changelog entry requested by author
Adds capability to define new MicroPython exception types, but no new exception is actually defined
No bounds-checking, memory-safety, or cryptographic changes visible in diff
Evidence from the diff
The change adds a new exception module in core/embed/rust/src/micropython/exception.rs that wraps MicroPython C APIs (mp_obj_new_exception, mp_obj_new_exception_args, etc.) and exposes typed constants for built-in exception types. It extends the obj_type! macro to support print_fn and parent fields, and refactors error.rs to call the new safe-ish wrappers instead of raw FFI. The build.rs allowlist is updated to expose mp_obj_exception_print, mp_obj_exception_attr, mp_obj_exception_make_new, and mp_type_Exception. The commit message labels it a refactor and explicitly notes ‘[no changelog]’.
Changed components
core/embed/rust/src/micropython/exception.rscore/embed/rust/src/micropython/macros.rscore/embed/rust/src/micropython/mod.rscore/embed/rust/src/error.rscore/embed/rust/build.rsInspect captured patch +104 / −38
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 3f9e38e4..e5e7c20f 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -374,9 +374,13 @@ fn generate_micropython_bindings() {
.allowlist_function("nlr_jump")
.allowlist_function("mp_obj_new_exception")
.allowlist_function("mp_obj_new_exception_args")
+ .allowlist_function("mp_obj_exception_print")
+ .allowlist_function("mp_obj_exception_attr")
+ .allowlist_function("mp_obj_exception_make_new")
.allowlist_function("trezor_obj_call_protected")
.allowlist_var("mp_type_AttributeError")
.allowlist_var("mp_type_EOFError")
+ .allowlist_var("mp_type_Exception")
.allowlist_var("mp_type_IndexError")
.allowlist_var("mp_type_KeyError")
.allowlist_var("mp_type_MemoryError")
diff --git a/core/embed/rust/src/error.rs b/core/embed/rust/src/error.rs
index 965eb68b..05762095 100644
--- a/core/embed/rust/src/error.rs
+++ b/core/embed/rust/src/error.rs
@@ -2,7 +2,11 @@ use core::{convert::Infallible, ffi::CStr, num::TryFromIntError};
#[cfg(feature = "micropython")]
use {
- crate::micropython::{ffi, obj::Obj, qstr::Qstr},
+ crate::micropython::{
+ exception::{self, new_exception, new_exception_arg_from, new_exception_args},
+ obj::Obj,
+ qstr::Qstr,
+ },
core::convert::TryInto,
};
@@ -49,44 +53,24 @@ impl Error {
// SAFETY: First argument is a reference to a valid exception type.
// EXCEPTION: Sensibly, `new_exception_*` does not raise.
match self {
- Error::TypeError => ffi::mp_obj_new_exception(&ffi::mp_type_TypeError),
- Error::OutOfRange => ffi::mp_obj_new_exception(&ffi::mp_type_OverflowError),
- Error::MissingKwargs => ffi::mp_obj_new_exception(&ffi::mp_type_TypeError),
- Error::AllocationFailed => ffi::mp_obj_new_exception(&ffi::mp_type_MemoryError),
- Error::IndexError => ffi::mp_obj_new_exception(&ffi::mp_type_IndexError),
+ Error::TypeError => new_exception(exception::TypeError),
+ Error::OutOfRange => new_exception(exception::OverflowError),
+ Error::MissingKwargs => new_exception(exception::TypeError),
+ Error::AllocationFailed => new_exception(exception::MemoryError),
+ Error::IndexError => new_exception(exception::IndexError),
Error::CaughtException(obj) => obj,
- Error::KeyError(key) => {
- ffi::mp_obj_new_exception_args(&ffi::mp_type_KeyError, 1, &key)
- }
- Error::ValueError(msg) => {
- if let Ok(msg) = msg.try_into() {
- ffi::mp_obj_new_exception_args(&ffi::mp_type_ValueError, 1, &msg)
- } else {
- ffi::mp_obj_new_exception(&ffi::mp_type_ValueError)
- }
- }
- Error::ValueErrorParam(msg, param) => {
- if let Ok(msg) = msg.try_into() {
- let args = [msg, param];
- ffi::mp_obj_new_exception_args(&ffi::mp_type_ValueError, 2, args.as_ptr())
- } else {
- ffi::mp_obj_new_exception(&ffi::mp_type_ValueError)
- }
- }
+ Error::KeyError(key) => new_exception_arg_from(exception::KeyError, key),
+ Error::ValueError(msg) => new_exception_arg_from(exception::ValueError, msg),
+ Error::ValueErrorParam(msg, param) => match msg.try_into() {
+ Ok(msg) => new_exception_args(exception::ValueError, &[msg, param]),
+ _ => new_exception(exception::ValueError),
+ },
Error::AttributeError(attr) => {
- ffi::mp_obj_new_exception_args(&ffi::mp_type_AttributeError, 1, &attr.into())
- }
- Error::EOFError => ffi::mp_obj_new_exception(&ffi::mp_type_EOFError),
- Error::RuntimeError(msg) => {
- if let Ok(msg) = msg.try_into() {
- ffi::mp_obj_new_exception_args(&ffi::mp_type_RuntimeError, 1, &msg)
- } else {
- ffi::mp_obj_new_exception(&ffi::mp_type_RuntimeError)
- }
- }
- Error::NotImplementedError => {
- ffi::mp_obj_new_exception(&ffi::mp_type_NotImplementedError)
+ new_exception_arg_from(exception::AttributeError, attr)
}
+ Error::EOFError => new_exception(exception::EOFError),
+ Error::RuntimeError(msg) => new_exception_arg_from(exception::RuntimeError, msg),
+ Error::NotImplementedError => new_exception(exception::NotImplementedError),
}
}
}
diff --git a/core/embed/rust/src/micropython/exception.rs b/core/embed/rust/src/micropython/exception.rs
new file mode 100644
index 00000000..80d62ad5
--- /dev/null
+++ b/core/embed/rust/src/micropython/exception.rs
@@ -0,0 +1,65 @@
+#![allow(non_upper_case_globals)]
+
+use super::{ffi, obj::Obj, qstr::Qstr, typ::Type};
+
+pub const AttributeError: &Type = unsafe { &ffi::mp_type_AttributeError };
+pub const EOFError: &Type = unsafe { &ffi::mp_type_EOFError };
+pub const Exception: &Type = unsafe { &ffi::mp_type_Exception };
+pub const IndexError: &Type = unsafe { &ffi::mp_type_IndexError };
+pub const KeyError: &Type = unsafe { &ffi::mp_type_KeyError };
+pub const MemoryError: &Type = unsafe { &ffi::mp_type_MemoryError };
+pub const NotImplementedError: &Type = unsafe { &ffi::mp_type_NotImplementedError };
+pub const OverflowError: &Type = unsafe { &ffi::mp_type_OverflowError };
+pub const RuntimeError: &Type = unsafe { &ffi::mp_type_RuntimeError };
+pub const TypeError: &Type = unsafe { &ffi::mp_type_TypeError };
+pub const ValueError: &Type = unsafe { &ffi::mp_type_ValueError };
+
+pub const fn define_exception(name: Qstr, parent: &Type) -> Type {
+ obj_type! {
+ name: name,
+ make_new_fn: ffi::mp_obj_exception_make_new,
+ attr_fn: ffi::mp_obj_exception_attr,
+ print_fn: ffi::mp_obj_exception_print,
+ parent: parent,
+ }
+}
+
+/// Create an exception instance. The result of this
+/// call should only be used to immediately raise the exception, because the
+/// object is not guaranteed to remain intact. MicroPython might reuse the
+/// same space for creating a different exception.
+///
+/// SAFETY: `exc_type` must be a reference to a valid exception type, otherwise
+/// micropython might abort.
+pub unsafe fn new_exception(exc_type: &'static Type) -> Obj {
+ // SAFETY: First argument is a reference to a valid exception type per
+ // precondition. EXCEPTION: Sensibly, `new_exception_*` does not raise.
+ unsafe { ffi::mp_obj_new_exception(exc_type) }
+}
+
+/// Create an exception instance. The result of this
+/// call should only be used to immediately raise the exception, because the
+/// object is not guaranteed to remain intact. MicroPython might reuse the
+/// same space for creating a different exception.
+///
+/// SAFETY: `exc_type` must be a reference to a valid exception type, otherwise
+/// micropython might abort.
+pub unsafe fn new_exception_args(exc_type: &'static Type, args: &[Obj]) -> Obj {
+ // SAFETY: First argument is a reference to a valid exception type per
+ // precondition. EXCEPTION: Sensibly, `new_exception_*` does not raise.
+ unsafe { ffi::mp_obj_new_exception_args(exc_type, args.len(), args.as_ptr()) }
+}
+
+/// Create an exception instance. The result of this
+/// call should only be used to immediately raise the exception, because the
+/// object is not guaranteed to remain intact. MicroPython might reuse the
+/// same space for creating a different exception.
+///
+/// SAFETY: `exc_type` must be a reference to a valid exception type, otherwise
+/// micropython might abort.
+pub unsafe fn new_exception_arg_from(exc_type: &'static Type, arg: impl TryInto<Obj>) -> Obj {
+ match arg.try_into() {
+ Ok(obj) => unsafe { new_exception_args(exc_type, &[obj]) },
+ _ => unsafe { new_exception(exc_type) },
+ }
+}
diff --git a/core/embed/rust/src/micropython/macros.rs b/core/embed/rust/src/micropython/macros.rs
index 9f8bb5bf..0e0804d2 100644
--- a/core/embed/rust/src/micropython/macros.rs
+++ b/core/embed/rust/src/micropython/macros.rs
@@ -115,6 +115,8 @@ macro_rules! obj_type {
$(make_new_fn: $make_new_fn:path,)?
$(attr_fn: $attr_fn:path,)?
$(call_fn: $call_fn:path,)?
+ $(print_fn: $print_fn:path,)?
+ $(parent: $parent:path,)?
) => {{
#[allow(unused_unsafe)]
unsafe {
@@ -142,6 +144,16 @@ macro_rules! obj_type {
let mut make_new: ffi::mp_make_new_fun_t = None;
$(make_new = Some($make_new_fn);)?
+ #[allow(unused_mut)]
+ #[allow(unused_assignments)]
+ let mut print: ffi::mp_print_fun_t = None;
+ $(print = Some($print_fn);)?
+
+ #[allow(unused_mut)]
+ #[allow(unused_assignments)]
+ let mut parent: *const cty::c_void = ::core::ptr::null_mut();
+ $(parent = $parent as *const _ as *mut _;)?
+
// TODO: This is safe only if we pass in `Dict` with fixed `Map` (created by
// `Map::fixed()`, usually through `obj_map!`), because only then will
// MicroPython treat `locals_dict` as immutable, and make the mutable cast safe.
@@ -156,7 +168,7 @@ macro_rules! obj_type {
},
flags: 0,
name,
- print: None,
+ print,
make_new,
call,
unary_op: None,
@@ -167,7 +179,7 @@ macro_rules! obj_type {
iternext: None,
buffer_p: ffi::mp_buffer_p_t { get_buffer: None },
protocol: ::core::ptr::null(),
- parent: ::core::ptr::null(),
+ parent,
locals_dict,
}
}
diff --git a/core/embed/rust/src/micropython/mod.rs b/core/embed/rust/src/micropython/mod.rs
index 49887713..5f23e2df 100644
--- a/core/embed/rust/src/micropython/mod.rs
+++ b/core/embed/rust/src/micropython/mod.rs
@@ -4,6 +4,7 @@ pub mod macros;
pub mod buffer;
pub mod dict;
+pub mod exception;
pub mod ffi;
pub mod func;
pub mod gc;
Why this scored 12/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.