What changed, and why it matters
This commit reworks how the Trezor Core firmware configures the memory protection unit (MPU) when switching to small helper programs called applets. It adds a dedicated thread-local storage (TLS) region for applets and fixes a bug where region 4 was being configured from the wrong layout field (code2 vs data2). The changes are framed by the developer as an improvement, not a security fix, and no external security references are provided.
Treat as a hardening/refactoring change with a latent bug fix. Review whether the old region 4 misconfiguration was reachable from untrusted applet code and whether the new TLS region exposes any applet data to other unprivileged contexts. No urgent patch action is indicated by the commit alone, but a follow-up security review of applet MPU boundaries is prudent.
Security signals we found
MPU region 4 previously used code2 start/size but data2 attributes; corrected to use code2 as FLASH_CODE or data2 as SRAM
New dedicated unprivileged SRAM region (#7) for applet thread-local storage in MPU_MODE_APP
Region 7 logic extracted and now dynamically updated when applet layout changes
No changelog entry and commit framed as 'improve', which can obscure security relevance
No CVE, advisory, or vendor security statement supplied
Evidence from the diff
The patch refactors MPU region management on STM32U5. Key changes: (1) adds tls to applet_layout_t; (2) replaces active_fb_addr/active_fb_size with an mpu_area_t struct; (3) stores the applet TLS area in drv->app_tls; (4) corrects a likely copy-paste bug in mpu_set_active_applet() where region 4 was previously set using layout->code2 values but with data2 permissions (now it uses code2 for code or data2 for data); (5) extracts region 7 update logic into mpu_update_region7() and, in MPU_MODE_APP, maps the applet TLS as SRAM read/write/unprivileged in region 7 instead of leaving it as privileged peripheral access. The commit message is a feature/improvement with no changelog and no security disclosure.
Changed components
core/embed/sys/mpu/stm32u5/mpu.ccore/embed/sys/mpu/inc/sys/mpu.hSTM32U5 MPU driverapplet memory layout and thread-local storage mappingInspect captured patch +59 / −25
diff --git a/core/embed/sys/mpu/inc/sys/mpu.h b/core/embed/sys/mpu/inc/sys/mpu.h
index ffc1bd777..bd31a94e5 100644
--- a/core/embed/sys/mpu/inc/sys/mpu.h
+++ b/core/embed/sys/mpu/inc/sys/mpu.h
@@ -85,6 +85,9 @@ typedef struct {
mpu_area_t code1;
// Read-only code area #2
mpu_area_t code2;
+ // Thread-local storage area
+ // (used only if not a part of data1 or data2)
+ mpu_area_t tls;
} applet_layout_t;
diff --git a/core/embed/sys/mpu/stm32u5/mpu.c b/core/embed/sys/mpu/stm32u5/mpu.c
index d42ff6a42..4f058996a 100644
--- a/core/embed/sys/mpu/stm32u5/mpu.c
+++ b/core/embed/sys/mpu/stm32u5/mpu.c
@@ -187,11 +187,11 @@ typedef struct {
bool initialized;
// Current mode
mpu_mode_t mode;
- // Address of the active framebuffer
- // (if set to 0, the framebuffer is not accessible)
- uint32_t active_fb_addr;
- // Size of the framebuffer in bytes
- size_t active_fb_size;
+ // Active framebuffer
+ // (if .addr is 0, the framebuffer is not accessible)
+ mpu_area_t active_fb;
+ // Applet thread-local storage area
+ mpu_area_t app_tls;
} mpu_driver_t;
@@ -200,6 +200,9 @@ mpu_driver_t g_mpu_driver = {
.mode = MPU_MODE_DISABLED,
};
+// forward declaration
+static void mpu_update_region7(mpu_mode_t mode);
+
static inline void mpu_disable(void) {
__DMB();
SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk;
@@ -323,6 +326,8 @@ void mpu_set_active_applet(applet_layout_t* layout) {
mpu_disable();
+ drv->app_tls = layout->tls;
+
if (layout != NULL) {
// clang-format off
if (layout->code1.start != 0 && layout->code1.size != 0) {
@@ -337,18 +342,27 @@ void mpu_set_active_applet(applet_layout_t* layout) {
DIS_REGION( 3 );
}
- if (layout->data2.start != 0 && layout->data2.size != 0) {
+ if (layout->code2.start != 0 && layout->code2.size != 0) {
+ SET_REGRUN( 4, layout->code2.start, layout->code2.size, FLASH_CODE, NO, YES );
+ } else if (layout->data2.start != 0 && layout->data2.size != 0) {
SET_REGRUN( 4, layout->data2.start, layout->data2.size, SRAM, YES, YES );
} else {
DIS_REGION( 4 );
}
// clang-format on
+
} else {
DIS_REGION(2);
DIS_REGION(3);
DIS_REGION(4);
}
+ // Remember the TLS area of the active applet
+ // (used in region #7 in MPU_APP mode)
+ drv->app_tls = layout->tls;
+
+ mpu_update_region7(drv->mode);
+
if (drv->mode != MPU_MODE_DISABLED) {
mpu_enable();
}
@@ -365,8 +379,8 @@ void mpu_set_active_fb(const void* addr, size_t size) {
irq_key_t lock = irq_lock();
- drv->active_fb_addr = (uint32_t)addr;
- drv->active_fb_size = size;
+ drv->active_fb.start = (uint32_t)addr;
+ drv->active_fb.size = size;
irq_unlock(lock);
@@ -384,8 +398,8 @@ bool mpu_inside_active_fb(const void* addr, size_t size) {
bool result =
((uintptr_t)addr + size >= (uintptr_t)addr) && // overflow check
- ((uintptr_t)addr >= drv->active_fb_addr) &&
- ((uintptr_t)addr + size <= drv->active_fb_addr + drv->active_fb_size);
+ ((uintptr_t)addr >= drv->active_fb.start) &&
+ ((uintptr_t)addr + size <= drv->active_fb.start + drv->active_fb.size);
irq_unlock(lock);
@@ -411,15 +425,15 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) {
switch (mode) {
case MPU_MODE_APP_SAES:
case MPU_MODE_APP:
- if (drv->active_fb_addr != 0) {
- SET_REGRUN( 5, drv->active_fb_addr, drv->active_fb_size, SRAM, YES, YES ); // Frame buffer
+ if (drv->active_fb.start != 0) {
+ SET_REGRUN( 5, drv->active_fb.start, drv->active_fb.size, SRAM, YES, YES );
} else {
DIS_REGION( 5 );
}
break;
default:
- if (drv->active_fb_addr != 0) {
- SET_REGRUN( 5, drv->active_fb_addr, drv->active_fb_size, SRAM, YES, NO ); // Frame buffer
+ if (drv->active_fb.start != 0) {
+ SET_REGRUN( 5, drv->active_fb.start, drv->active_fb.size, SRAM, YES, NO );
} else {
DIS_REGION( 5 );
}
@@ -490,6 +504,26 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) {
// Region #7 is banked
+ mpu_update_region7(mode);
+
+ if (mode != MPU_MODE_DISABLED) {
+ mpu_enable();
+ }
+
+ mpu_mode_t prev_mode = drv->mode;
+ drv->mode = mode;
+
+ irq_unlock(irq_key);
+
+ return prev_mode;
+}
+
+// Must be called with IRQs disabled and MPU disabled
+static void mpu_update_region7(mpu_mode_t mode) {
+#ifdef KERNEL
+ mpu_driver_t* drv = &g_mpu_driver;
+#endif
+
// clang-format off
switch (mode) {
// REGION ADDRESS SIZE TYPE WRITE UNPRIV
@@ -499,6 +533,14 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) {
// access to secure SAES and TAMPER peripherals in unprivileged mode.
SET_REGION( 7, PERIPH_BASE_S, SIZE_256M, PERIPHERAL, YES, YES );
break;
+
+ case MPU_MODE_APP:
+ if (drv->app_tls.start != 0 && drv->app_tls.size != 0) {
+ SET_REGRUN( 7, drv->app_tls.start, drv->app_tls.size, SRAM, YES, YES );
+ } else {
+ DIS_REGION( 7 );
+ }
+ break;
#endif
default:
// All peripherals (Privileged, Read-Write, Non-Executable)
@@ -506,17 +548,6 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) {
break;
}
// clang-format on
-
- if (mode != MPU_MODE_DISABLED) {
- mpu_enable();
- }
-
- mpu_mode_t prev_mode = drv->mode;
- drv->mode = mode;
-
- irq_unlock(irq_key);
-
- return prev_mode;
}
void mpu_restore(mpu_mode_t mode) { mpu_reconfig(mode); }
Why this scored 41/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.