What changed, and why it matters
This commit adds a new internal API surface called 'coreapp API' that lets the core firmware expose a small set of system services (timers, events, debug console, IPC, and shutdown helpers) to a separate 'coreapp' component. It is only enabled for the T3W1 model and is guarded by an 'app_loading' build feature. The change is architectural: it wires a function-pointer getter into the vector table and stores it in the coreapp header so the coreapp can request versioned API tables. There is no direct evidence in the commit of a vulnerability, exploit, or security fix; it appears to be a feature introduction for future app-loading support.
Treat this as a new trusted-computing boundary that needs a security design review. Before the `app_loading` feature is used in production, audit the exposed API for confused-deputy problems, ensure all coreapp-facing handlers validate arguments and caller identity, verify that the vector-table slot is read-only and correctly located, and confirm that the coreapp cannot use these APIs to escape its sandbox or influence the kernel unexpectedly. No immediate patch is indicated by the diff alone.
Security signals we found
New privileged API surface exposed to a less-privileged 'coreapp' context
API table includes process termination, fatal error, debug console, IPC, and system-handle read functions
STM32U5 vector table now publishes a fixed function pointer (`coreapp_api_get`) to the coreapp
No visible access-control, argument validation, or sandboxing logic in the introduced code
Feature is model-gated to T3W1 and build-gated by `app_loading`/`USE_APP_LOADING`
Commit message and diff contain no security claims, changelog entry, or bug/fix references
Evidence from the diff
The patch introduces trezor_api.h, trezor_api_v1.h, and trezor_api_v1_impl.c under core/embed/api/, defining a versioned API table (trezor_api_v1_t) and a getter coreapp_api_get(). The SConscripts add this source only when the app_loading feature is wanted, which is gated on TREZOR_MODEL == 'T3W1'. The STM32U5 vector table is extended to place coreapp_api_get at a fixed slot when USE_APP_LOADING is defined; STM32F4 gets only placeholder zeros with comments. The coreapp task structure gains an api_getter field, populated from the applet header on STM32 and set to coreapp_api_get on the Unix emulator. The API table exposes privileged operations such as system_exit*, sysevents_poll, syshandle_read, and IPC primitives to the coreapp. No validation logic, capability checks, or security policy are visible in this commit.
Changed components
core/embed/api/trezor_api.hcore/embed/api/trezor_api_v1.hcore/embed/api/trezor_api_v1_impl.ccore/embed/sys/startup/stm32f4/vectortable.Score/embed/sys/startup/stm32u5/vectortable.Score/embed/sys/task/inc/sys/coreapp.hcore/embed/sys/task/stm32/coreapp.ccore/embed/sys/task/unix/coreapp.ccore/SConscript.firmwarecore/SConscript.kernelcore/SConscript.unixInspect captured patch +195 / −6
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 618d6d5c..34e16832 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -88,6 +88,9 @@ if PYOPT == '0':
if DBG_CONSOLE != "":
FEATURES_WANTED += ["dbg_console"]
+if TREZOR_MODEL in ['T3W1']:
+ FEATURES_WANTED += ["app_loading"]
+
CCFLAGS_MOD = ''
CPPPATH_MOD = []
CPPDEFINES_MOD = []
@@ -276,6 +279,9 @@ SOURCE_MOD += [
if not PRODUCTION:
SOURCE_MOD += ['embed/rtl/printf.c']
+if "app_loading" in FEATURES_WANTED:
+ SOURCE_MOD += ['embed/api/trezor_api_v1_impl.c']
+
CPPDEFINES_MOD += [
'TRANSLATIONS',
'FANCY_FATAL_ERROR',
@@ -485,6 +491,7 @@ env.Replace(
ALLPATHS = [
'.',
+ 'embed/api',
'embed/rust',
'embed/projects/firmware',
'embed/rtl/inc',
diff --git a/core/SConscript.kernel b/core/SConscript.kernel
index f9580f44..812657c4 100644
--- a/core/SConscript.kernel
+++ b/core/SConscript.kernel
@@ -82,6 +82,9 @@ if DBG_CONSOLE != "":
if not TREZOR_MODEL in ['T3W1', 'D002']:
FEATURES_WANTED += ["secure_mode"]
+if TREZOR_MODEL in ['T3W1']:
+ FEATURES_WANTED += ["app_loading"]
+
if DISABLE_OPTIGA:
# TODO use PYOPT instead of PRODUCTION, same as in firmware, blocked on #4253
if PRODUCTION:
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 3355de86..e17d8cad 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -56,6 +56,9 @@ if BITCOIN_ONLY == '0':
if not DISABLE_TROPIC:
FEATURES_WANTED.append('tropic')
+if TREZOR_MODEL in ['T3W1']:
+ FEATURES_WANTED += ["app_loading"]
+
if not models.has_emulator(TREZOR_MODEL):
# skip unix build
env = Environment()
@@ -421,6 +424,10 @@ SOURCE_UNIX = [
'vendor/micropython/shared/runtime/gchelper_generic.c',
]
+if "app_loading" in FEATURES_WANTED:
+ SOURCE_UNIX += ['embed/api/trezor_api_v1_impl.c']
+
+
TRANSLATION_DATA = [
"translations/en.json",
"translations/order.json",
diff --git a/core/embed/api/trezor_api.h b/core/embed/api/trezor_api.h
new file mode 100644
index 00000000..fc6ee11d
--- /dev/null
+++ b/core/embed/api/trezor_api.h
@@ -0,0 +1,24 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include "trezor_api_v1.h"
+
+typedef void* (*trezor_api_getter_t)(uint32_t version);
diff --git a/core/embed/api/trezor_api_v1.h b/core/embed/api/trezor_api_v1.h
new file mode 100644
index 00000000..208e1094
--- /dev/null
+++ b/core/embed/api/trezor_api_v1.h
@@ -0,0 +1,75 @@
+/*
+ * 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/>.
+ */
+
+#pragma once
+
+#include <sys/sysevent.h>
+#include <sys/system.h>
+#include <sys/systick.h>
+
+#ifdef USE_DBG_CONSOLE
+#include <sys/dbg_console.h>
+#endif
+
+#ifdef USE_IPC
+#include <sys/ipc.h>
+#endif
+
+#ifndef USE_DBG_CONSOLE
+// temporary hack to allow compilation when DBG console is disabled
+ssize_t dbg_console_write(const void* data, size_t data_size);
+#endif
+
+typedef struct {
+ void (*system_exit)(int exitcode);
+
+ void (*system_exit_error)(const char* title, const char* message,
+ const char* footer);
+
+ void (*system_exit_error_ex)(const char* title, size_t title_len,
+ const char* message, size_t message_len,
+ const char* footer, size_t footer_len);
+
+ void (*system_exit_fatal)(const char* message, const char* file, int line);
+
+ void (*system_exit_fatal_ex)(const char* message, size_t message_len,
+ const char* file, size_t file_len, int line);
+
+ ssize_t (*dbg_console_write)(const void* data, size_t size);
+
+ uint32_t (*systick_ms)(void);
+
+ void (*sysevents_poll)(const sysevents_t* awaited, sysevents_t* signalled,
+ uint32_t deadline);
+
+ ssize_t (*syshandle_read)(syshandle_t handle, void* buffer,
+ size_t buffer_size);
+
+ bool (*ipc_register)(systask_id_t remote, void* buffer, size_t size);
+
+ void (*ipc_unregister)(systask_id_t remote);
+
+ bool (*ipc_try_receive)(ipc_message_t* msg);
+
+ void (*ipc_message_free)(ipc_message_t* msg);
+
+ bool (*ipc_send)(systask_id_t remote, uint32_t fn, const void* data,
+ size_t data_size);
+
+} trezor_api_v1_t;
diff --git a/core/embed/api/trezor_api_v1_impl.c b/core/embed/api/trezor_api_v1_impl.c
new file mode 100644
index 00000000..d90994f2
--- /dev/null
+++ b/core/embed/api/trezor_api_v1_impl.c
@@ -0,0 +1,51 @@
+/*
+ * 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_api_v1.h"
+
+#ifndef USE_DBG_CONSOLE
+// temporary hack to allow compilation when DBG console is disabled
+ssize_t dbg_console_write(const void* data, size_t data_size) {
+ return data_size;
+}
+#endif
+
+const trezor_api_v1_t trezor_api_v1 = {
+ .system_exit = system_exit,
+ .system_exit_error = system_exit_error,
+ .system_exit_error_ex = system_exit_error_ex,
+ .system_exit_fatal = system_exit_fatal,
+ .system_exit_fatal_ex = system_exit_fatal_ex,
+ .systick_ms = systick_ms,
+ .sysevents_poll = sysevents_poll,
+ .syshandle_read = syshandle_read,
+ .dbg_console_write = dbg_console_write,
+ .ipc_register = ipc_register,
+ .ipc_unregister = ipc_unregister,
+ .ipc_try_receive = ipc_try_receive,
+ .ipc_message_free = ipc_message_free,
+ .ipc_send = ipc_send,
+};
+
+const void* coreapp_api_get(uint32_t version) {
+ if (version == 1) {
+ return &trezor_api_v1;
+ }
+ return NULL;
+}
diff --git a/core/embed/sys/startup/stm32f4/vectortable.S b/core/embed/sys/startup/stm32f4/vectortable.S
index 31e81760..f996e2e1 100644
--- a/core/embed/sys/startup/stm32f4/vectortable.S
+++ b/core/embed/sys/startup/stm32f4/vectortable.S
@@ -135,12 +135,12 @@ vector_table:
.word reset_handler
.word _stack_section_start
.word _stack_section_size
- .word 0
- .word 0
- .word 0
- .word 0
- .word 0
-
+ .word 0 // TLS start
+ .word 0 // TLS size
+ .word 0 // API interface getter
+ .word 0 // SAES unpriv input
+ .word 0 // SAES unpriv output
+ .word 0 // SAES unpriv callback
#endif
.end
diff --git a/core/embed/sys/startup/stm32u5/vectortable.S b/core/embed/sys/startup/stm32u5/vectortable.S
index 6ab99c43..ccce9c69 100644
--- a/core/embed/sys/startup/stm32u5/vectortable.S
+++ b/core/embed/sys/startup/stm32u5/vectortable.S
@@ -185,6 +185,11 @@ vector_table:
.word _stack_section_size
.word _tls_section_start
.word _tls_section_size
+#ifdef USE_APP_LOADING
+ .word coreapp_api_get
+#else
+ .word 0
+#endif
#if USE_STORAGE_HWKEY
.word saes_unpriv_input
.word saes_unpriv_output
diff --git a/core/embed/sys/task/inc/sys/coreapp.h b/core/embed/sys/task/inc/sys/coreapp.h
index 492ddf10..3667a716 100644
--- a/core/embed/sys/task/inc/sys/coreapp.h
+++ b/core/embed/sys/task/inc/sys/coreapp.h
@@ -37,6 +37,8 @@ typedef struct {
mpu_area_t stack;
// TLS area
mpu_area_t tls;
+ // API interface getter
+ void* api_getter;
// Unprivileged SAES input buffer
void* saes_input;
// Unprivileged SAES output buffer
@@ -72,4 +74,8 @@ mpu_area_t coreapp_get_tls_area(void);
#endif // TREZOR_EMULATOR
+#ifdef USE_APP_LOADING
+void* coreapp_get_api_getter(void);
+#endif
+
#endif // KERNEL_MODE
diff --git a/core/embed/sys/task/stm32/coreapp.c b/core/embed/sys/task/stm32/coreapp.c
index 5b2c071b..a6f4f628 100644
--- a/core/embed/sys/task/stm32/coreapp.c
+++ b/core/embed/sys/task/stm32/coreapp.c
@@ -31,6 +31,7 @@
static mpu_area_t coreapp_code_area;
static mpu_area_t coreapp_tls_area;
+static void* coreapp_api_getter = NULL;
// defined in linker script
extern uint32_t _kernel_flash_end;
@@ -89,6 +90,7 @@ bool coreapp_init(applet_t* applet, uint32_t cmd, const void* arg,
// (we will need then later for extension applets)
coreapp_tls_area = header->tls;
coreapp_code_area = applet->layout.code1;
+ coreapp_api_getter = header->api_getter;
// Reset the applet task (stack pointer, etc.)
if (!systask_init(&applet->task, header->stack.start, header->stack.size, 0,
@@ -119,4 +121,6 @@ mpu_area_t coreapp_get_code_area(void) { return coreapp_code_area; }
mpu_area_t coreapp_get_tls_area(void) { return coreapp_tls_area; }
+void* coreapp_get_api_getter(void) { return coreapp_api_getter; }
+
#endif // KERNEL
diff --git a/core/embed/sys/task/unix/coreapp.c b/core/embed/sys/task/unix/coreapp.c
index b0368e1e..0b8792ff 100644
--- a/core/embed/sys/task/unix/coreapp.c
+++ b/core/embed/sys/task/unix/coreapp.c
@@ -27,6 +27,9 @@
extern int coreapp_emu(int argc, char** argv);
+// API getter function implemented in the coreapp
+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};
@@ -45,4 +48,8 @@ bool coreapp_init(applet_t* applet, int argc, char** argv) {
return true;
}
+#ifdef USE_APP_LOADING
+void* coreapp_get_api_getter(void) { return (void*)coreapp_api_get; }
+#endif
+
#endif // KERNEL
Why this scored 25/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.