feat(core): introduce framebuffer_access applet privilege
What changed, and why it matters
This commit adds a new security permission called framebuffer_access that controls whether a small helper app (applet) running on the Trezor can read from or write to the device's screen memory. Previously, applets could access the framebuffer based only on memory location checks. Now they must also be explicitly granted the framebuffer_access privilege. The change is a hardening/feature addition, not a fix for an active exploit, and it removes an older privilege called assets_area_access that was apparently unused or being replaced.
Treat as a security-hardening change. Review whether any existing applets besides coreapp legitimately need framebuffer access and ensure the privilege is granted only where required. Verify that mpu_update_region5 is invoked consistently whenever fb_access or active_fb state changes, and confirm the removed assets_area_access privilege was not relied upon elsewhere in the codebase.
Security signals we found
New capability-based privilege (framebuffer_access) added for applet framebuffer access
Syscall probes now gate framebuffer read/write on both privilege flag and MPU region membership
MPU region 5 configuration now depends on the new fb_access flag in APP/APP_SAES modes
Removal/replacement of the previous assets_area_access privilege field
coreapp explicitly granted framebuffer_access; third-party applets default to no framebuffer access
Evidence from the diff
The patch introduces an applet_privileges_t.framebuffer_access boolean and threads it through the MPU/syscall layers. mpu_set_active_applet now takes a fb_access flag; on STM32U5 this flag is stored in the MPU driver and used in mpu_update_region5 to decide whether to enable an unprivileged-accessible MPU region covering the framebuffer. probe_read_access and probe_write_access additionally require the privilege before allowing framebuffer access. coreapp is granted framebuffer_access; ordinary apps loaded by app_loader are not (fb_access=false). The old assets_area_access field is removed/replaced.
Changed components
core/embed/sys/mpu (STM32U5, STM32F4, Unix MPU drivers)core/embed/sys/syscall/stm32/syscall_probe.ccore/embed/sys/task (applet header, coreapp, systask)core/embed/io/app_arena (app_loader, app_arena)Inspect captured patch +53 / −35
### core/embed/io/app_arena/app_arena.c
@@ -285,7 +285,7 @@ static void app_arena_configure_mpu(const app_arena_entry_t* entry) {
applet_layout_t layout = {
.data1 = {.start = (uintptr_t)entry->mem_ptr, .size = entry->mem_size},
};
- mpu_set_active_applet(&layout);
+ mpu_set_active_applet(&layout, false);
#endif
}
### core/embed/io/app_arena/stm32u5/app_loader.c
@@ -280,7 +280,7 @@ static ts_t fit_in_memory(const app_code_header_t* chdr, void* data,
// Callback invoked when applet is unloaded
static void unload_cb(applet_t* applet) {
- mpu_set_active_applet(&applet->layout);
+ mpu_set_active_applet(&applet->layout, false);
// Clear RW segment, stack and the heap to remove any sensitive information
// before freeing the memory
@@ -522,7 +522,7 @@ ts_t app_loader_prepare_applet(const app_header_t* header, void* code,
};
// Enable access to applet memory regions
- mpu_set_active_applet(&applet->layout);
+ mpu_set_active_applet(&applet->layout, false);
// Initialize the applet task
bool ok = systask_init(&applet->task, map.stack_p_addr, map.stack_size,
### core/embed/sys/mpu/inc/sys/mpu.h
@@ -93,7 +93,7 @@ typedef struct {
// Sets the MPU to allow unprivileged access to the given applet
// (just one applet at a time can be visible)
-void mpu_set_active_applet(const applet_layout_t* layout);
+void mpu_set_active_applet(const applet_layout_t* layout, bool fb_access);
// Sets the MPU to allow access to the
// framebuffer at the given address and size.
### core/embed/sys/mpu/stm32f4/mpu.c
@@ -203,7 +203,7 @@ mpu_mode_t mpu_get_mode(void) {
return drv->mode;
}
-void mpu_set_active_applet(const applet_layout_t* layout) {
+void mpu_set_active_applet(const applet_layout_t* layout, bool fb_access) {
// On STM32F4 one coreapp applet is allowed to run at a time
}
### core/embed/sys/mpu/stm32u5/mpu.c
@@ -209,6 +209,8 @@ typedef struct {
mpu_area_t active_fb;
// Applet thread-local storage area
mpu_area_t app_tls;
+ // Applet has access to the framebuffer
+ bool app_fb_access;
} mpu_driver_t;
@@ -217,7 +219,8 @@ mpu_driver_t g_mpu_driver = {
.mode = MPU_MODE_DISABLED,
};
-// forward declaration
+// forward declarations
+static void mpu_update_region5(mpu_mode_t mode);
static void mpu_update_region7(mpu_mode_t mode);
static inline void mpu_disable(void) {
@@ -324,7 +327,7 @@ mpu_mode_t mpu_get_mode(void) {
return drv->mode;
}
-void mpu_set_active_applet(const applet_layout_t* layout) {
+void mpu_set_active_applet(const applet_layout_t* layout, bool fb_access) {
mpu_driver_t* drv = &g_mpu_driver;
if (!drv->initialized) {
@@ -388,6 +391,10 @@ void mpu_set_active_applet(const applet_layout_t* layout) {
// (used in region #7 in MPU_APP mode)
drv->app_tls = layout ? layout->tls : (mpu_area_t){0};
+ // Remember whether the active applet has access to the framebuffer
+ drv->app_fb_access = fb_access;
+
+ mpu_update_region5(drv->mode);
mpu_update_region7(drv->mode);
if (drv->mode != MPU_MODE_DISABLED) {
@@ -451,25 +458,7 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) {
// Region #5 is banked
- // clang-format off
- switch (mode) {
- case MPU_MODE_APP_SAES:
- case MPU_MODE_APP:
- 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.start != 0) {
- SET_REGRUN( 5, drv->active_fb.start, drv->active_fb.size, SRAM, YES, NO );
- } else {
- DIS_REGION( 5 );
- }
- break;
- }
- // clang-format on
+ mpu_update_region5(mode);
// Region #6 is banked
@@ -548,6 +537,31 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) {
return prev_mode;
}
+// Must be called with IRQs disabled and MPU disabled
+static void mpu_update_region5(mpu_mode_t mode) {
+ mpu_driver_t* drv = &g_mpu_driver;
+
+ // clang-format off
+ switch (mode) {
+ case MPU_MODE_APP_SAES:
+ case MPU_MODE_APP:
+ if (drv->app_fb_access && 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.start != 0) {
+ SET_REGRUN( 5, drv->active_fb.start, drv->active_fb.size, SRAM, YES, NO );
+ } else {
+ DIS_REGION( 5 );
+ }
+ break;
+ }
+ // clang-format on
+}
+
// Must be called with IRQs disabled and MPU disabled
static void mpu_update_region7(mpu_mode_t mode) {
#ifdef KERNEL
### core/embed/sys/mpu/unix/mpu.c
@@ -29,4 +29,4 @@ mpu_mode_t mpu_reconfig(mpu_mode_t mode) { return MPU_MODE_DISABLED; }
void mpu_restore(mpu_mode_t mode) {}
-void mpu_set_active_applet(const applet_layout_t* layout) {}
+void mpu_set_active_applet(const applet_layout_t* layout, bool fb_access) {}
### core/embed/sys/syscall/stm32/syscall_probe.c
@@ -66,7 +66,8 @@ bool probe_read_access(const void *addr, size_t len) {
}
#ifdef FRAMEBUFFER
- if (mpu_inside_active_fb(addr, len)) {
+ if (applet->privileges.framebuffer_access &&
+ mpu_inside_active_fb(addr, len)) {
return true;
}
#endif
@@ -121,7 +122,8 @@ bool probe_write_access(void *addr, size_t len) {
}
#ifdef FRAMEBUFFER
- if (mpu_inside_active_fb(addr, len)) {
+ if (applet->privileges.framebuffer_access &&
+ mpu_inside_active_fb(addr, len)) {
return true;
}
#endif
### core/embed/sys/task/inc/sys/applet.h
@@ -30,7 +30,8 @@ typedef struct applet applet_t;
/** Applet privileges */
typedef struct {
- bool assets_area_access;
+ // Applet is allowed to access the active framebuffer
+ bool framebuffer_access;
} applet_privileges_t;
/** Callback called when an applet is unloaded */
### core/embed/sys/task/stm32/coreapp.c
@@ -67,9 +67,9 @@ static void applet_set_unpriv(applet_t* applet, bool unpriv) {
static void coreapp_unload_cb(applet_t* applet) {
// Clear all memory the applet was allowed to use
- mpu_set_active_applet(&applet->layout);
+ mpu_set_active_applet(&applet->layout, false);
coreapp_clear_memory(applet);
- mpu_set_active_applet(NULL);
+ mpu_set_active_applet(NULL, false);
#ifdef USE_TRUSTZONE
// Disable unprivileged access to the coreapp memory regions
applet_set_unpriv(applet, false);
@@ -102,15 +102,15 @@ bool coreapp_init(applet_t* applet, uint32_t cmd, const void* arg,
};
applet_privileges_t coreapp_privileges = {
- .assets_area_access = true,
+ .framebuffer_access = true,
};
applet_init(applet, &coreapp_privileges, coreapp_unload_cb);
applet->layout = coreapp_layout;
// Enable access to coreapp memory regions
- mpu_set_active_applet(&applet->layout);
+ mpu_set_active_applet(&applet->layout, false);
// Clear all memory the applet is allowed to use
coreapp_clear_memory(applet);
### core/embed/sys/task/stm32/systask.c
@@ -194,7 +194,8 @@ systask_id_t systask_id(const systask_t* task) { return task->id; }
void systask_set_mpu(systask_t* task) {
if (task->applet != NULL) {
applet_t* applet = (applet_t*)task->applet;
- mpu_set_active_applet(&applet->layout);
+ mpu_set_active_applet(&applet->layout,
+ applet->privileges.framebuffer_access);
}
}
#endif // USE_APPLETSWhy this scored 46/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.