feat(core): add `alloc` support for Miniscript
What changed, and why it matters
This commit adds a new memory allocator for the Miniscript feature in Trezor firmware. It routes Rust memory allocations through MicroPython's existing 'tracked allocator' so memory can be freed explicitly instead of waiting for garbage collection. The commit message says this avoids 'false positives' from the garbage collector, meaning the old approach could incorrectly report memory as still in use. There is no direct evidence in the commit of a security vulnerability; it reads as a reliability/performance improvement.
Treat as a normal feature/refactoring commit. Review the new allocator for correctness under alignment requirements and OOM behavior during Miniscript operations, but no immediate security response is indicated by the supplied materials.
Security signals we found
Memory allocator change in security-critical hardware wallet firmware
OOM path explicitly panics (RSOD) rather than failing gracefully
Alignment requirement is not guaranteed by underlying allocator; checked with ensure! in debug builds only
Commit message frames change as avoiding GC false positives, not as fixing a vulnerability
Evidence from the diff
The change introduces a Rust GlobalAlloc implementation (TrackedAllocator) backed by MicroPython’s m_tracked_calloc/m_tracked_free. It is enabled only when the ‘miniscript’ feature is active, via a new ‘tracked_allocator’ feature flag. The allocator does not support custom alignment (falls back to GC alignment) and panics on OOM because Rust currently lacks fallible allocation. The commit adds the necessary Cargo features, build.rs bindings, mpconfigport.h defines, and allocator wiring. No bug fix, bounds check, or input validation change is visible in the diff.
Changed components
core/embed/rust/src/allocator.rscore/embed/rust/src/micropython/tracked_allocator.rscore/embed/projects/firmware/Cargo.tomlcore/embed/projects/firmware/src/stm32/mpconfigport.hcore/embed/projects/firmware/src/unix/mpconfigport.hcore/embed/rust/Cargo.tomlcore/embed/rust/build.rscore/embed/rust/src/lib.rscore/embed/rust/src/micropython/mod.rscore/embed/upymod/Cargo.tomlcore/embed/upymod/build.rsInspect captured patch +76 / −1
### core/embed/projects/firmware/Cargo.toml
@@ -40,7 +40,7 @@ frozen = ["upymod/frozen"]
log_stack_usage = ["upymod/log_stack_usage"]
memperf = ["upymod/memperf"]
micropy_enable_source_lines = ["upymod/enable_source_lines"]
-miniscript = ["trezor_lib/miniscript", "upymod/miniscript", "ward"]
+miniscript = ["trezor_lib/miniscript", "upymod/miniscript", "tracked_allocator", "ward"]
n1w1 = ["upymod/n1w1"]
optiga_testing = ["sec/optiga_testing"]
production = ["io/production"]
@@ -173,3 +173,4 @@ default = [
mcu_stm32f4 = ["mcu_stm32"]
mcu_stm32u5 = ["sec/trustzone", "mcu_stm32"]
mcu_stm32 = []
+tracked_allocator = ["trezor_lib/tracked_allocator", "upymod/tracked_allocator"]
### core/embed/projects/firmware/src/stm32/mpconfigport.h
@@ -42,6 +42,7 @@
#define MICROPY_ENABLE_PYSTACK (1)
#define MICROPY_LOADED_MODULES_DICT_SIZE (160)
#define MICROPY_MAIN_DICT_SIZE (8)
+#define MICROPY_TRACKED_ALLOC (USE_TRACKED_ALLOCATOR)
// emitters
#define MICROPY_PERSISTENT_CODE_LOAD (0)
### core/embed/projects/firmware/src/unix/mpconfigport.h
@@ -47,6 +47,7 @@
#define MICROPY_ENABLE_PYSTACK (1)
#define MICROPY_LOADED_MODULES_DICT_SIZE (160)
#define MICROPY_MAIN_DICT_SIZE (8)
+#define MICROPY_TRACKED_ALLOC (USE_TRACKED_ALLOCATOR)
// emitters
#define MICROPY_PERSISTENT_CODE_LOAD (0)
### core/embed/rust/Cargo.toml
@@ -95,6 +95,7 @@ telemetry = []
thp = ["crypto/thp", "dep:trezor-thp", "dep:zeroize"]
touch = []
touch_wakeup = []
+tracked_allocator = []
translations = ["crypto"]
tropic = []
ui_antialiasing = []
### core/embed/rust/build.rs
@@ -99,6 +99,8 @@ fn generate_micropython_bindings(lib: &mut CLibrary) -> Result<()> {
.allowlist_function("gc_alloc")
.allowlist_function("gc_free")
.allowlist_var("GC_ALLOC_FLAG_HAS_FINALISER")
+ .allowlist_function("m_tracked_calloc")
+ .allowlist_function("m_tracked_free")
// iter
.allowlist_type("mp_obj_iter_buf_t")
.allowlist_function("mp_getiter")
### core/embed/rust/src/allocator.rs
@@ -0,0 +1,4 @@
+use crate::micropython::tracked_allocator::TrackedAllocator;
+
+#[global_allocator]
+static ALLOCATOR: TrackedAllocator = TrackedAllocator;
### core/embed/rust/src/lib.rs
@@ -11,6 +11,7 @@
#![allow(dead_code)]
#![feature(const_trait_impl)]
#![feature(custom_test_frameworks)]
+#![feature(pointer_is_aligned_to)]
#![no_main]
#![reexport_test_harness_main = "test_main"]
#![cfg_attr(
@@ -29,6 +30,9 @@ extern crate num_derive;
#[macro_use]
mod macros;
+#[cfg(all(feature = "tracked_allocator", feature = "micropython"))]
+mod allocator;
+
mod align;
#[cfg(feature = "debug")]
mod coverage;
### core/embed/rust/src/micropython/mod.rs
@@ -30,6 +30,9 @@ pub use obj::Obj;
#[cfg(feature = "miniscript")]
mod miniscript;
+#[cfg(feature = "tracked_allocator")]
+pub mod tracked_allocator;
+
#[cfg(feature = "dbg_console")]
pub mod logging;
### core/embed/rust/src/micropython/tracked_allocator.rs
@@ -0,0 +1,53 @@
+use core::alloc::{GlobalAlloc, Layout};
+#[cfg(feature = "debug")]
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+use cty::c_void;
+
+use super::ffi;
+
+pub struct TrackedAllocator;
+
+#[cfg(feature = "debug")]
+static TOTAL: AtomicUsize = AtomicUsize::new(0);
+
+// TODO: allow allocations only in an explicit scope?
+
+unsafe impl GlobalAlloc for TrackedAllocator {
+ unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+ let size = layout.size();
+ // SAFETY:
+ // - Unfortunately we cannot respect `layout.align()` as MicroPython GC does
+ // not support custom alignment.
+ // - `raw` is guaranteed to stay valid as long as `m_tracked_free()` is not
+ // called.
+ // EXCEPTION: Returns null instead of raising.
+ let raw: *mut c_void = unsafe { ffi::m_tracked_calloc(1, size) };
+ if !raw.is_null() {
+ #[cfg(feature = "debug")]
+ log::trace!(
+ "{:?} = {} : +{}",
+ raw,
+ TOTAL.fetch_add(size, Ordering::Relaxed),
+ size
+ );
+ ensure!(raw.is_aligned_to(layout.align()), "Unaligned allocation");
+ }
+ raw as _
+ }
+ unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
+ let raw = ptr as *mut c_void;
+ #[cfg(feature = "debug")]
+ {
+ let size = _layout.size();
+ log::trace!(
+ "{:?} = {} : -{}",
+ raw,
+ TOTAL.fetch_sub(size, Ordering::Relaxed),
+ size
+ );
+ }
+ // SAFETY: We are the sole owner of the allocated value.
+ unsafe { ffi::m_tracked_free(raw) };
+ }
+}
### core/embed/upymod/Cargo.toml
@@ -61,6 +61,7 @@ pyopt = []
rgb_led = ["io/rgb_led"]
sd_card = ["io/sd_card"]
serial_number = []
+tracked_allocator = []
telemetry = ["sec/telemetry"]
thp = []
touch = ["io/touch"]
### core/embed/upymod/build.rs
@@ -72,6 +72,10 @@ fn main() -> Result<()> {
lib.add_define("USE_MINISCRIPT", Some("1"));
}
+ if cfg!(feature = "tracked_allocator") {
+ lib.add_define("USE_TRACKED_ALLOCATOR", Some("1"));
+ }
+
if cfg!(feature = "n1w1") {
lib.add_define("USE_N1W1", Some("1"));
}Why this scored 16/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.