What changed, and why it matters
This commit rewrites how the Trezor firmware's kernel cleans up after the main user-interface app (the 'coreapp') finishes or crashes. It replaces a simple 'stop' function with a new 'unload' path that always clears the app's memory and, on real hardware, disables the special memory/peripheral access rights that were granted while the app ran. The change also adds a check so the kernel only tries to clean up tasks that were actually initialized. Overall it looks like a hardening/cleanup improvement rather than a new vulnerability, though the commit is a refactor and the diff alone cannot prove no bugs were introduced.
Treat as a normal code-quality/security-hardening change. Reviewers should verify that coreapp_unload_cb() is always invoked before the next coreapp is loaded, that mpu_set_active_applet(NULL) cannot leave the MPU in an unexpectedly permissive state, and that the new 'initialized' flag is set atomically enough relative to systask_is_alive() checks. No immediate security response is indicated by the diff alone.
Security signals we found
Memory-clearing and privilege-revocation logic moved into a single unload callback, reducing the chance that a code path forgets to reset TrustZone/MPU permissions
New systask_t.initialized guard prevents operations on partially-initialized task structures
applet_unload() now terminates the task if it is still alive before releasing resources, which may reduce windows where a live task retains access after cleanup begins
Refactor removes duplicated stm32/unix applet.c logic, lowering maintenance risk but also changing execution order of security-relevant teardown
Evidence from the diff
The patch unifies the previously platform-specific applet.c implementations (stm32/applet.c and unix/applet.c) into a single core/embed/sys/task/applet.c. The old applet_stop() is replaced by applet_unload(), which: (1) calls systask_exit() if the task is still alive, (2) invokes a platform-specific unload_cb if registered, and (3) clears the callback. On STM32, coreapp_unload_cb() now performs the memory-zeroing and TrustZone unprivilege operations that used to happen around applet_run()/applet_stop(). The coreapp_init() path now sets the layout after applet_init(), enables unprivileged access before running, and registers coreapp_unload_cb. A new ‘initialized’ flag is added to systask_t and systask_is_alive() now returns false for uninitialized tasks, preventing applet_unload() from trying to exit a task that was never set up. Build files are updated to compile the new shared applet.c instead of the per-platform files.
Changed components
core/embed/sys/task/applet.c (new shared implementation)core/embed/sys/task/inc/sys/applet.hcore/embed/sys/task/stm32/coreapp.ccore/embed/sys/task/stm32/systask.ccore/embed/sys/task/unix/coreapp.ccore/embed/sys/task/unix/systask.ccore/embed/projects/kernel/main.ccore/site_scons/models/stm32f4_common.pycore/site_scons/models/stm32u5_common.pycore/site_scons/models/unix_common.pyInspect captured patch +146 / −158
diff --git a/core/embed/projects/kernel/main.c b/core/embed/projects/kernel/main.c
index b6aafe78e..5f2a720bc 100644
--- a/core/embed/projects/kernel/main.c
+++ b/core/embed/projects/kernel/main.c
@@ -231,7 +231,7 @@ static void show_rsod(const systask_postmortem_t *pminfo) {
// Loop until the coreapp is terminated
kernel_loop(&coreapp);
// Release the coreapp resources
- applet_stop(&coreapp);
+ applet_unload(&coreapp);
if (coreapp.task.pminfo.reason == TASK_TERM_REASON_EXIT) {
// RSOD was shown successfully
@@ -297,7 +297,7 @@ int main(void) {
// Loop until the coreapp is terminated
kernel_loop(&coreapp);
// Release the coreapp resources
- applet_stop(&coreapp);
+ applet_unload(&coreapp);
#ifndef USE_BOOTARGS_RSOD
// Coreapp crashed, show RSOD
diff --git a/core/embed/sys/task/applet.c b/core/embed/sys/task/applet.c
new file mode 100644
index 000000000..a19467cf1
--- /dev/null
+++ b/core/embed/sys/task/applet.c
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_rtl.h>
+
+#include <sys/applet.h>
+#include <sys/systask.h>
+
+#ifdef KERNEL
+
+void applet_init(applet_t* applet, const applet_privileges_t* privileges,
+ applet_unload_cb_t unload_cb) {
+ memset(applet, 0, sizeof(applet_t));
+
+ applet->unload_cb = unload_cb;
+
+ if (privileges != NULL) {
+ applet->privileges = *privileges;
+ }
+}
+
+void applet_run(applet_t* applet) { systask_yield_to(&applet->task); }
+
+void applet_unload(applet_t* applet) {
+ if (systask_is_alive(&applet->task)) {
+ systask_exit(&applet->task, 0);
+ }
+
+ if (applet->unload_cb != NULL) {
+ applet->unload_cb(applet);
+ applet->unload_cb = NULL;
+ }
+}
+
+bool applet_is_alive(applet_t* applet) {
+ return systask_is_alive(&applet->task);
+}
+
+applet_t* applet_active(void) {
+ systask_t* task = systask_active();
+
+ if (task == NULL) {
+ return NULL;
+ }
+
+ return (applet_t*)task->applet;
+}
+
+#endif // KERNEL
diff --git a/core/embed/sys/task/inc/sys/applet.h b/core/embed/sys/task/inc/sys/applet.h
index 7f8ba0ace..d95987714 100644
--- a/core/embed/sys/task/inc/sys/applet.h
+++ b/core/embed/sys/task/inc/sys/applet.h
@@ -25,24 +25,35 @@
#include <sys/systask.h>
+/** Applet structure */
+typedef struct applet applet_t;
+
/** Applet privileges */
typedef struct {
bool assets_area_access;
} applet_privileges_t;
-typedef struct {
- /** Applet memory layout describing the memory areas
- * the applet is allowed to use */
- applet_layout_t layout;
+/** Callback called when an applet is unloaded */
+typedef void (*applet_unload_cb_t)(applet_t* applet);
+
+struct applet {
/** Applet privileges */
applet_privileges_t privileges;
+
/** Task associated with the applet */
systask_t task;
+ /** Callback called when the applet is unloaded */
+ applet_unload_cb_t unload_cb;
+
#ifdef TREZOR_EMULATOR
/** Handle returned by `dlopen()` */
void* handle;
+#else
+ /** Applet memory layout describing the memory areas
+ * the applet is allowed to use */
+ applet_layout_t layout;
#endif
-} applet_t;
+};
/**
* @brief Initializes the applet structure
@@ -51,11 +62,12 @@ typedef struct {
* initializing the task associated with the applet.
*
* @param applet Pointer to the applet to initialize.
- * @param layout Pointer to the applet memory layout.
* @param privileges Pointer to the applet privileges.
+ * @param unload_cb Callback called when the applet is unloaded.
+ *
*/
-void applet_init(applet_t* applet, const applet_layout_t* layout,
- const applet_privileges_t* privileges);
+void applet_init(applet_t* applet, const applet_privileges_t* privileges,
+ applet_unload_cb_t unload_cb);
/**
* @brief Runs the applet task first time.
@@ -69,10 +81,10 @@ void applet_init(applet_t* applet, const applet_layout_t* layout,
void applet_run(applet_t* applet);
/**
- * @brief Release all resources held by the applet
+ * @brief Releases all resources held by the applet
* @param applet Pointer to the applet to stop.
*/
-void applet_stop(applet_t* applet);
+void applet_unload(applet_t* applet);
/**
* @brief Returns `true` if the applet task is alive.
diff --git a/core/embed/sys/task/inc/sys/systask.h b/core/embed/sys/task/inc/sys/systask.h
index 3d0f15fe4..17866ded0 100644
--- a/core/embed/sys/task/inc/sys/systask.h
+++ b/core/embed/sys/task/inc/sys/systask.h
@@ -149,6 +149,8 @@ typedef struct {
/** Set to nonzero, if the task is killed */
volatile uint32_t killed;
+ /** The structure is properly initialized */
+ bool initialized;
/** Task id */
systask_id_t id;
/** Task post-mortem information */
diff --git a/core/embed/sys/task/stm32/applet.c b/core/embed/sys/task/stm32/applet.c
deleted file mode 100644
index ab267c92a..000000000
--- a/core/embed/sys/task/stm32/applet.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <trezor_model.h>
-#include <trezor_rtl.h>
-
-#include <io/display.h>
-#include <sys/applet.h>
-#include <sys/mpu.h>
-#include <sys/rng.h>
-#include <sys/systask.h>
-
-#ifdef USE_TRUSTZONE
-#include <sys/trustzone.h>
-#endif
-
-#ifdef KERNEL
-
-void applet_init(applet_t* applet, const applet_layout_t* layout,
- const applet_privileges_t* privileges) {
- memset(applet, 0, sizeof(applet_t));
-
- applet->layout = *layout;
- applet->privileges = *privileges;
-}
-
-#ifdef USE_TRUSTZONE
-// Sets unprivileged access to the applet memory regions
-// and allows applet to use some specific peripherals.
-static void applet_set_unpriv(applet_t* applet, bool unpriv) {
- applet_layout_t* layout = &applet->layout;
-
- tz_set_sram_unpriv(layout->data1.start, layout->data1.size, unpriv);
- tz_set_sram_unpriv(layout->data2.start, layout->data2.size, unpriv);
- tz_set_flash_unpriv(layout->code1.start, layout->code1.size, unpriv);
- tz_set_flash_unpriv(layout->code2.start, layout->code2.size, unpriv);
-
- if (applet->privileges.assets_area_access) {
- tz_set_flash_unpriv(ASSETS_START, ASSETS_MAXSIZE, unpriv);
- }
-
- display_set_unpriv_access(unpriv);
-}
-#endif // USE_TRUSTZONE
-
-void applet_run(applet_t* applet) {
-#ifdef USE_TRUSTZONE
- applet_set_unpriv(applet, true);
-#endif
-
- systask_yield_to(&applet->task);
-}
-
-void applet_stop(applet_t* applet) {
-#ifdef USE_TRUSTZONE
- applet_set_unpriv(applet, false);
-#endif
-}
-
-bool applet_is_alive(applet_t* applet) {
- return systask_is_alive(&applet->task);
-}
-
-applet_t* applet_active(void) {
- systask_t* task = systask_active();
-
- if (task == NULL) {
- return NULL;
- }
-
- return (applet_t*)task->applet;
-}
-
-#endif // KERNEL
diff --git a/core/embed/sys/task/stm32/coreapp.c b/core/embed/sys/task/stm32/coreapp.c
index a6f4f6283..bd0daff0e 100644
--- a/core/embed/sys/task/stm32/coreapp.c
+++ b/core/embed/sys/task/stm32/coreapp.c
@@ -29,6 +29,11 @@
#include <sys/rng.h>
#include <sys/systask.h>
+#ifdef USE_TRUSTZONE
+#include <io/display.h>
+#include <sys/trustzone.h>
+#endif
+
static mpu_area_t coreapp_code_area;
static mpu_area_t coreapp_tls_area;
static void* coreapp_api_getter = NULL;
@@ -46,6 +51,34 @@ static void coreapp_clear_memory(applet_t* applet) {
}
}
+#ifdef USE_TRUSTZONE
+// Sets unprivileged access to the applet memory regions
+// and allows applet to use some specific peripherals.
+static void applet_set_unpriv(applet_t* applet, bool unpriv) {
+ applet_layout_t* layout = &applet->layout;
+
+ tz_set_sram_unpriv(layout->data1.start, layout->data1.size, unpriv);
+ tz_set_sram_unpriv(layout->data2.start, layout->data2.size, unpriv);
+ tz_set_flash_unpriv(layout->code1.start, layout->code1.size, unpriv);
+ tz_set_flash_unpriv(layout->code2.start, layout->code2.size, unpriv);
+
+ tz_set_flash_unpriv(ASSETS_START, ASSETS_MAXSIZE, unpriv);
+
+ display_set_unpriv_access(unpriv);
+}
+#endif // USE_TRUSTZONE
+
+static void coreapp_unload_cb(applet_t* applet) {
+ // Clear all memory the applet was allowed to use
+ mpu_set_active_applet(&applet->layout);
+ coreapp_clear_memory(applet);
+ mpu_set_active_applet(NULL);
+#ifdef USE_TRUSTZONE
+ // Disable unprivileged access to the coreapp memory regions
+ applet_set_unpriv(applet, false);
+#endif
+}
+
bool coreapp_init(applet_t* applet, uint32_t cmd, const void* arg,
size_t arg_size) {
const uint32_t CODE1_START = KERNEL_END;
@@ -75,7 +108,9 @@ bool coreapp_init(applet_t* applet, uint32_t cmd, const void* arg,
.assets_area_access = true,
};
- applet_init(applet, &coreapp_layout, &coreapp_privileges);
+ applet_init(applet, &coreapp_privileges, coreapp_unload_cb);
+
+ applet->layout = coreapp_layout;
// Enable access to coreapp memory regions
mpu_set_active_applet(&applet->layout);
@@ -83,6 +118,11 @@ bool coreapp_init(applet_t* applet, uint32_t cmd, const void* arg,
// Clear all memory the applet is allowed to use
coreapp_clear_memory(applet);
+#ifdef USE_TRUSTZONE
+ // Enable unprivileged access to the coreapp memory regions
+ applet_set_unpriv(applet, true);
+#endif
+
const coreapp_header_t* header =
(coreapp_header_t*)applet->layout.code1.start;
diff --git a/core/embed/sys/task/stm32/systask.c b/core/embed/sys/task/stm32/systask.c
index 4da4b2831..a40627e2c 100644
--- a/core/embed/sys/task/stm32/systask.c
+++ b/core/embed/sys/task/stm32/systask.c
@@ -180,6 +180,8 @@ bool systask_init(systask_t* task, uint32_t stack_base, uint32_t stack_size,
task->applet = applet;
task->sb_addr = sb_addr;
+ task->initialized = true;
+
// Notify all event sources about the task creation
sysevents_notify_task_created(task);
@@ -359,7 +361,9 @@ static void systask_kill(systask_t* task) {
}
}
-bool systask_is_alive(const systask_t* task) { return !task->killed; }
+bool systask_is_alive(const systask_t* task) {
+ return task->initialized && !task->killed;
+}
void systask_exit(systask_t* task, int exit_code) {
systask_scheduler_t* scheduler = &g_systask_scheduler;
diff --git a/core/embed/sys/task/unix/applet.c b/core/embed/sys/task/unix/applet.c
deleted file mode 100644
index 8d1877603..000000000
--- a/core/embed/sys/task/unix/applet.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <trezor_rtl.h>
-
-#include <sys/applet.h>
-
-void applet_init(applet_t* applet, const applet_layout_t* layout,
- const applet_privileges_t* privileges) {
- memset(applet, 0, sizeof(applet_t));
-
- applet->layout = *layout;
- applet->privileges = *privileges;
-}
-
-void applet_run(applet_t* applet) { systask_yield_to(&applet->task); }
-
-void applet_stop(applet_t* applet) {}
-
-bool applet_is_alive(applet_t* applet) {
- return systask_is_alive(&applet->task);
-}
-
-applet_t* applet_active(void) {
- systask_t* task = systask_active();
-
- if (task == NULL) {
- return NULL;
- }
-
- return (applet_t*)task->applet;
-}
diff --git a/core/embed/sys/task/unix/coreapp.c b/core/embed/sys/task/unix/coreapp.c
index 0b8792ff7..60477abe7 100644
--- a/core/embed/sys/task/unix/coreapp.c
+++ b/core/embed/sys/task/unix/coreapp.c
@@ -31,10 +31,9 @@ extern int coreapp_emu(int argc, char** argv);
extern const void* coreapp_api_get(uint32_t version);
bool coreapp_init(applet_t* applet, int argc, char** argv) {
- const applet_layout_t coreapp_layout = {0};
const applet_privileges_t coreapp_privileges = {0};
- applet_init(applet, &coreapp_layout, &coreapp_privileges);
+ applet_init(applet, &coreapp_privileges, NULL);
if (!systask_init(&applet->task, 0, 0, 0, applet)) {
return false;
diff --git a/core/embed/sys/task/unix/systask.c b/core/embed/sys/task/unix/systask.c
index 903db9c73..b5f84df20 100644
--- a/core/embed/sys/task/unix/systask.c
+++ b/core/embed/sys/task/unix/systask.c
@@ -205,6 +205,8 @@ bool systask_init(systask_t* task, uint32_t stack_base, uint32_t stack_size,
return false;
}
+ task->initialized = true;
+
// Notify all event sources about the task creation
sysevents_notify_task_created(task);
@@ -251,7 +253,9 @@ static void systask_kill(systask_t* task) {
}
}
-bool systask_is_alive(const systask_t* task) { return !task->killed; }
+bool systask_is_alive(const systask_t* task) {
+ return task->initialized && !task->killed;
+}
void systask_exit(systask_t* task, int exit_code) {
systask_scheduler_t* scheduler = &g_systask_scheduler;
diff --git a/core/site_scons/models/stm32f4_common.py b/core/site_scons/models/stm32f4_common.py
index 0afaebba0..6cccd60e6 100644
--- a/core/site_scons/models/stm32f4_common.py
+++ b/core/site_scons/models/stm32f4_common.py
@@ -101,12 +101,12 @@ def stm32f4_common_files(env, features_wanted, defines, sources, paths):
"embed/sys/syscall/stm32/syscall_probe.c",
"embed/sys/syscall/stm32/syscall_stubs.c",
"embed/sys/syscall/stm32/syscall_verifiers.c",
- "embed/sys/task/stm32/applet.c",
"embed/sys/task/stm32/coreapp.c",
"embed/sys/task/stm32/systask.c",
"embed/sys/task/stm32/system.c",
"embed/sys/time/stm32/systick.c",
"embed/sys/time/stm32/systimer.c",
+ "embed/sys/task/applet.c",
"embed/sys/task/sysevent.c",
"embed/sys/task/system.c",
]
diff --git a/core/site_scons/models/stm32u5_common.py b/core/site_scons/models/stm32u5_common.py
index f5c8d79d0..03d5a9b48 100644
--- a/core/site_scons/models/stm32u5_common.py
+++ b/core/site_scons/models/stm32u5_common.py
@@ -168,7 +168,7 @@ def stm32u5_common_files(env, features_wanted, defines, sources, paths):
paths += ["embed/sys/ipc/inc"]
if "applet" in features_wanted:
- sources += ["embed/sys/task/stm32/applet.c"]
+ sources += ["embed/sys/task/applet.c"]
sources += ["embed/sys/task/stm32/coreapp.c"]
if "app_loading" in features_wanted:
diff --git a/core/site_scons/models/unix_common.py b/core/site_scons/models/unix_common.py
index d5feeaa44..bb7e2679b 100644
--- a/core/site_scons/models/unix_common.py
+++ b/core/site_scons/models/unix_common.py
@@ -99,7 +99,7 @@ def unix_common_files(env, features_wanted, defines, sources, paths):
paths += ["embed/sys/ipc/inc"]
if "applet" in features_wanted:
- sources += ["embed/sys/task/unix/applet.c"]
+ sources += ["embed/sys/task/applet.c"]
sources += ["embed/sys/task/unix/coreapp.c"]
if "app_loading" in features_wanted:
Why this scored 32/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.