feat(core): introduce unlimited_syscall applet privilege
What changed, and why it matters
This commit adds a new security gate inside the Trezor firmware's core operating system. Before this change, any installed mini-program ('applet') could call any internal system function ('syscall'). Now, by default, applets can only call a small, approved list of syscalls, unless they are explicitly granted a new 'unlimited_syscalls' privilege. The built-in 'coreapp' applet is granted this privilege, so it behaves as before. The change is a hardening measure that reduces the damage a compromised or malicious applet could do.
No immediate user action is required. This is a hardening commit. Developers should ensure the whitelist in syscall_dispatch.c remains synchronized with trezor_api_v1.h as both evolve, and review whether coreapp truly needs unlimited_syscalls or can be migrated to the restricted set over time.
Security signals we found
New syscall whitelist enforced in syscall_handler
New applet privilege bit `unlimited_syscalls`
coreapp granted unlimited_syscalls; other applets default to restricted
Forbidden syscalls terminate applet via system_exit_fatal
Comment explicitly ties whitelist to trezor_api_v1_t public API surface
Evidence from the diff
The patch introduces an unlimited_syscalls flag in applet_privileges_t and a syscall_is_allowed() whitelist in the ARM STM32 syscall dispatcher. The dispatcher now rejects any syscall not in the whitelist unless the calling applet has unlimited_syscalls. The whitelist is documented as needing to stay in sync with trezor_api_v1_t. The coreapp applet is initialized with unlimited_syscalls = true, preserving its existing behavior. This is a defense-in-depth sandboxing change, not a fix for a known exploitable bug.
Changed components
core/embed/sys/syscall/stm32/syscall_dispatch.ccore/embed/sys/task/inc/sys/applet.hcore/embed/sys/task/stm32/coreapp.ccore/embed/api/trezor_api_v1.hInspect captured patch +46 / −1
### core/embed/api/trezor_api_v1.h
@@ -36,6 +36,11 @@
ssize_t dbg_console_write(const void* data, size_t data_size);
#endif
+// Applets without the `unlimited_syscalls` privilege may only invoke
+// the syscalls backing the functions exposed through `trezor_api_v1_t`.
+// Keep this API in sync with the syscall whitelist in
+// `syscall_is_allowed()` (syscall_dispatch.c).
+
typedef struct {
void (*system_exit)(int exitcode);
### core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -98,9 +98,46 @@
#include "syscall_internal.h"
#include "syscall_verifiers.h"
+static inline bool syscall_is_allowed(const applet_t *applet,
+ uint32_t syscall) {
+ if (applet == NULL) {
+ return false;
+ }
+
+ if (applet->privileges.unlimited_syscalls) {
+ return true;
+ }
+
+ // Applets without the `unlimited_syscalls` privilege may only
+ // invoke the syscalls listed below - the subset exposed through
+ // `trezor_api_v1_t`. Keep this list in sync with trezor_api_v1.h.
+ switch (syscall) {
+ case SYSCALL_SYSTEM_EXIT:
+ case SYSCALL_SYSTEM_EXIT_ERROR:
+ case SYSCALL_SYSTEM_EXIT_FATAL:
+ case SYSCALL_SYSTICK_MS:
+ case SYSCALL_SYSEVENTS_POLL:
+ case SYSCALL_SYSLOG_START_RECORD:
+ case SYSCALL_SYSLOG_WRITE_CHUNK:
+ case SYSCALL_IPC_REGISTER:
+ case SYSCALL_IPC_UNREGISTER:
+ case SYSCALL_IPC_TRY_RECEIVE:
+ case SYSCALL_IPC_FREE_MESSAGE:
+ case SYSCALL_IPC_SEND:
+ case SYSCALL_APP_GET_HEAP:
+ return true;
+ }
+
+ return false;
+}
+
__attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
uint32_t syscall,
void *applet) {
+ if (!syscall_is_allowed((applet_t *)applet, syscall)) {
+ system_exit_fatal("Forbidden syscall", __FILE_NAME__, __LINE__);
+ }
+
syscall_set_context((applet_t *)applet);
switch (syscall) {
### core/embed/sys/task/inc/sys/applet.h
@@ -30,8 +30,10 @@ typedef struct applet applet_t;
/** Applet privileges */
typedef struct {
- // Applet is allowed to access the active framebuffer
+ // Applet is allowed to access the active framebuffer
bool framebuffer_access;
+ // Applet is allowed to use any syscall
+ bool unlimited_syscalls;
} applet_privileges_t;
/** Callback called when an applet is unloaded */
### core/embed/sys/task/stm32/coreapp.c
@@ -103,6 +103,7 @@ bool coreapp_init(applet_t* applet, uint32_t cmd, const void* arg,
applet_privileges_t coreapp_privileges = {
.framebuffer_access = true,
+ .unlimited_syscalls = true,
};
applet_init(applet, &coreapp_privileges, coreapp_unload_cb);Why 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.