What changed, and why it matters
This commit adds a brand-new inter-process communication (IPC) subsystem to the Trezor firmware kernel. It lets separate software tasks send fixed-size messages to each other through kernel-managed queues, with memory copies that temporarily disable the memory-protection unit on STM32 hardware. There is no claim in the commit that this fixes a security bug; it appears to be a new feature. The code includes access checks (verifiers) for the user-space syscall path, but because this is a new and complex kernel mechanism, any mistakes in the queue logic or memory copying could become security issues in the future.
Treat this as a feature commit, not a security patch. If auditing, focus on the correctness of the queue compaction in ipc_message_free, the alignment and size checks in ipc_try_receive/ipc_send, and the safety of temporarily disabling the MPU inside ipc_memcpy. A follow-up review should verify that probe_read_access/probe_write_access correctly cover the entire IPC buffer and that no race exists between queue state changes and syshandle signaling.
Security signals we found
New kernel-mode IPC subsystem with privileged memory-copy primitive (ipc_memcpy) that disables MPU on STM32
Syscall verifiers added for ipc_register, ipc_try_receive, ipc_message_free, ipc_send
ipc_message_free treats msg->data as an opaque token rather than re-validating the pointer
Queue bounds checks present in ipc_try_receive and ipc_send, but ring-buffer compaction logic in ipc_message_free is new and correctness depends on invariants maintained by sender/receiver
No vendor disclosure of security relevance, no CVE, no advisory, no attribution in commit materials
Evidence from the diff
The patch introduces core/embed/sys/ipc/, an IPC layer built on top of the systask/syshandle event framework. Key components: (1) ipc.h defines a 64 KiB max buffer, ipc_message_t, and APIs for register/unregister, try_receive, message_free, and send. (2) ipc.c implements a ring-buffer-like queue per (target, origin) task pair, with items aligned to sizeof(size_t). It registers per-task syshandles (SYSHANDLE_IPC0..2) and provides VMT callbacks for task creation and read-ready signaling. (3) Platform-specific ipc_memcpy temporarily disables the MPU on stm32u5 to copy across task memory domains; the Unix port is a plain memcpy. (4) Syscall numbers, dispatch, stubs, and verifiers wire the IPC APIs into the privileged syscall path, using probe_read_access/probe_write_access to validate caller buffers. (5) SCons files add the feature to firmware, kernel, and unix builds. No changelog entry is present. No external references or CVEs are supplied.
Changed components
core/embed/sys/ipc/ipc.ccore/embed/sys/ipc/stm32u5/ipc_memcpy.ccore/embed/sys/ipc/unix/ipc_memcpy.ccore/embed/sys/syscall/stm32/syscall_dispatch.ccore/embed/sys/syscall/stm32/syscall_stubs.ccore/embed/sys/syscall/stm32/syscall_verifiers.ccore/embed/sys/syscall/stm32/syscall_verifiers.hcore/embed/sys/task/inc/sys/sysevent.hcore/embed/sys/task/stm32/system.ccore/SConscript.firmwarecore/SConscript.kernelcore/SConscript.unixInspect captured patch +677 / −3
diff --git a/core/SConscript.firmware b/core/SConscript.firmware
index 31fdffbc..618d6d5c 100644
--- a/core/SConscript.firmware
+++ b/core/SConscript.firmware
@@ -57,6 +57,7 @@ FEATURES_WANTED = [
"dma2d",
"haptic",
"input",
+ "ipc",
"optiga",
"power_manager",
"rgb_led",
diff --git a/core/SConscript.kernel b/core/SConscript.kernel
index c7fca3d0..f9580f44 100644
--- a/core/SConscript.kernel
+++ b/core/SConscript.kernel
@@ -50,6 +50,7 @@ FEATURES_WANTED = [
"dma2d",
"haptic",
"input",
+ "ipc",
"kernel_mode",
"optiga",
"power_manager",
diff --git a/core/SConscript.unix b/core/SConscript.unix
index b31d8da5..3355de86 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -28,6 +28,7 @@ FEATURES_WANTED = [
"display",
"dma2d",
"input",
+ "ipc",
"kernel_mode",
"optiga",
"powerctl",
diff --git a/core/embed/sys/ipc/inc/sys/ipc.h b/core/embed/sys/ipc/inc/sys/ipc.h
new file mode 100644
index 00000000..d9256082
--- /dev/null
+++ b/core/embed/sys/ipc/inc/sys/ipc.h
@@ -0,0 +1,109 @@
+/*
+ * 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_types.h>
+
+#include <sys/systask.h>
+
+// Maximum size of the IPC message buffer that can be registered
+#define IPC_MAX_BUFFER_SIZE (64 * 1024)
+
+typedef struct {
+ systask_id_t remote;
+ // Function code with flags (IPC_FN_xxx)
+ uint32_t fn;
+ // Pointer to the message payload data
+ const void *data;
+ // Size of the payload data
+ size_t size;
+} ipc_message_t;
+
+#ifdef KERNEL_MODE
+
+/**
+ * @brief Initializes the IPC subsystem.
+ *
+ * Internal function called during system startup.
+ *
+ * @return true if the IPC subsystem was successfully initialized
+ */
+bool ipc_init(void);
+
+#endif // KERNEL_MODE
+
+/**
+ * @brief Registers a buffer for receiving IPC messages from a specific task.
+ *
+ * Buffer must be aligned to sizeof(size_t) bytes, otherwise the registration
+ * will fail.
+ *
+ * @param remote The remote task ID to register the buffer for.
+ * @param buffer Pointer to the buffer to use for receiving messages.
+ * @param size Size of the buffer in bytes. Must be less or equal to
+ * IPC_MAX_BUFFER_SIZE.
+ * @return true if the buffer was successfully registered
+ *
+ */
+bool ipc_register(systask_id_t remote, void *buffer, size_t size);
+
+/**
+ * @brief Unregisters the IPC message buffer for the given task ID.
+ *
+ * @param remote The remote task ID to unregister the buffer for.
+ *
+ */
+void ipc_unregister(systask_id_t remote);
+
+/**
+ * @brief Attempts to receive an IPC message without blocking.
+ *
+ * @param msg Pointer to an `ipc_message_t` structure to store the received
+ * message.
+ * @return true if a message was received and stored in `msg`
+ */
+bool ipc_try_receive(ipc_message_t *msg);
+
+/**
+ * @brief Releases resources associated with a received IPC message.
+ *
+ * This function should be called sooner or later for every message
+ * received via `ipc_receive` or `ipc_try_receive`.
+ *
+ * @param msg Pointer to the freed `ipc_message_t` structure.
+ */
+void ipc_message_free(ipc_message_t *msg);
+
+/**
+ * @brief Sends an IPC message to the specified destination task.
+ *
+ * This function is non-blocking and returns immediately.
+ * The call succeeds only if the remote task has registered a buffer for
+ * receiving messages and there is enough space in that buffer.
+ *
+ * @param remote The destination task ID to send the message to.
+ * @param fn The function code for the message.
+ * @param data Pointer to the message payload data.
+ * @param data_size Size of the payload data in bytes.
+ *
+ * @return true if the message was successfully sent
+ */
+bool ipc_send(systask_id_t remote, uint32_t fn, const void *data,
+ size_t data_size);
diff --git a/core/embed/sys/ipc/ipc.c b/core/embed/sys/ipc/ipc.c
new file mode 100644
index 00000000..2333c535
--- /dev/null
+++ b/core/embed/sys/ipc/ipc.c
@@ -0,0 +1,292 @@
+/*
+ * 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/ipc.h>
+#include <sys/sysevent.h>
+#include <sys/systick.h>
+
+#ifdef KERNEL_MODE
+
+#include <rtl/sizedefs.h>
+#include <sys/sysevent_source.h>
+#include <sys/systask.h>
+
+#include "ipc_memcpy.h"
+
+// Alignment for IPC data within the queue
+#define IPC_DATA_ALIGNMENT (sizeof(size_t))
+
+typedef struct {
+ uint8_t free;
+ systask_id_t remote;
+ uint32_t fn;
+ size_t size;
+ uint8_t __attribute__((aligned(IPC_DATA_ALIGNMENT))) data[];
+} ipc_queue_item_t;
+
+typedef struct {
+ uint8_t *ptr;
+ uint8_t *wptr;
+ uint8_t *rptr;
+ size_t size;
+} ipc_queue_t;
+
+typedef struct {
+ bool initialized;
+ // [target][origin]
+ ipc_queue_t queue[SYSTASK_MAX_TASKS][SYSTASK_MAX_TASKS];
+} ipc_driver_t;
+
+static ipc_driver_t g_ipc_driver = {
+ .initialized = false,
+};
+
+// forward declaration
+static const syshandle_vmt_t g_ipc_handle_vmt;
+
+bool ipc_init(void) {
+ ipc_driver_t *drv = &g_ipc_driver;
+
+ if (drv->initialized) {
+ return true;
+ }
+
+ memset(drv, 0, sizeof(ipc_driver_t));
+
+ for (systask_id_t task_id = 0; task_id < SYSTASK_MAX_TASKS; task_id++) {
+ syshandle_t handle = SYSHANDLE_IPC0 + task_id;
+ void *context = (void *)(uintptr_t)task_id;
+ if (!syshandle_register(handle, &g_ipc_handle_vmt, context)) {
+ return false;
+ }
+ }
+
+ drv->initialized = true;
+ return true;
+}
+
+ipc_queue_t *ipc_queue(systask_id_t target, systask_id_t origin) {
+ ipc_driver_t *drv = &g_ipc_driver;
+
+ if (!drv->initialized) {
+ return NULL;
+ }
+
+ if (target >= SYSTASK_MAX_TASKS || origin >= SYSTASK_MAX_TASKS) {
+ return NULL;
+ }
+
+ return &drv->queue[target][origin];
+}
+
+bool ipc_register(systask_id_t remote, void *buffer, size_t size) {
+ systask_id_t target = systask_id(systask_active());
+ ipc_queue_t *queue = ipc_queue(target, remote);
+
+ if (queue == NULL) {
+ return false;
+ }
+
+ if (size > IPC_MAX_BUFFER_SIZE) {
+ return false;
+ }
+
+ if (!IS_ALIGNED((uintptr_t)buffer, IPC_DATA_ALIGNMENT)) {
+ // Buffer is not properly aligned
+ return false;
+ }
+
+ queue->ptr = buffer;
+ queue->size = size;
+ queue->wptr = buffer;
+ queue->rptr = buffer;
+ return true;
+}
+
+void ipc_unregister(systask_id_t remote) {
+ systask_id_t target = systask_id(systask_active());
+ ipc_queue_t *queue = ipc_queue(target, remote);
+ if (queue != NULL) {
+ memset(queue, 0, sizeof(ipc_queue_t));
+ }
+}
+
+bool ipc_try_receive(ipc_message_t *msg) {
+ systask_id_t target = systask_id(systask_active());
+
+ ipc_queue_t *queue = ipc_queue(target, msg->remote);
+
+ if (queue == NULL || queue->ptr == NULL) {
+ // Invalid target or no queue registered
+ return false;
+ }
+
+ if (queue->wptr - queue->rptr < sizeof(ipc_queue_item_t)) {
+ // No messages available
+ return false;
+ }
+
+ ipc_queue_item_t *item = (ipc_queue_item_t *)queue->rptr;
+
+ if (queue->wptr - queue->rptr - sizeof(ipc_queue_item_t) <
+ ALIGN_UP(item->size, IPC_DATA_ALIGNMENT)) {
+ // Invalid item size
+ return false;
+ }
+
+ msg->fn = item->fn;
+ msg->data = item->data;
+ msg->size = item->size;
+
+ // Move read pointer to the next item
+ queue->rptr +=
+ sizeof(ipc_queue_item_t) + ALIGN_UP(item->size, IPC_DATA_ALIGNMENT);
+
+ return true;
+}
+
+void ipc_message_free(ipc_message_t *msg) {
+ systask_id_t target = systask_id(systask_active());
+
+ ipc_queue_t *queue = ipc_queue(target, msg->remote);
+
+ if (queue == NULL || queue->ptr == NULL) {
+ // Invalid target or no queue registered
+ return;
+ }
+
+ ipc_queue_item_t *item = (ipc_queue_item_t *)queue->ptr;
+ ipc_queue_item_t *new_wptr = item;
+
+ while (item < (ipc_queue_item_t *)queue->wptr) {
+ size_t remaining_size = (uint8_t *)queue->wptr - (uint8_t *)item;
+
+ if (remaining_size < sizeof(ipc_queue_item_t) ||
+ item->size > remaining_size - sizeof(ipc_queue_item_t)) {
+ // Invalid item size => queue corruption
+ return;
+ }
+
+ // Mark the item as free if it matches the data pointer
+ if (item->data == msg->data) {
+ item->free = true;
+ }
+
+ bool advance_wptr = !item->free;
+
+ // Move to next item
+ size_t item_size =
+ ALIGN_UP(sizeof(ipc_queue_item_t) + item->size, IPC_DATA_ALIGNMENT);
+ item = (ipc_queue_item_t *)((uint8_t *)item + item_size);
+
+ if (advance_wptr) {
+ new_wptr = item;
+ }
+ }
+
+ queue->wptr = (uint8_t *)new_wptr;
+
+ if (queue->wptr < queue->rptr) {
+ queue->rptr = queue->wptr;
+ }
+}
+
+bool ipc_send(systask_id_t remote, uint32_t fn, const void *data,
+ size_t data_size) {
+ systask_id_t origin = systask_id(systask_active());
+
+ ipc_queue_t *queue = ipc_queue(remote, origin);
+
+ if (queue == NULL || queue->ptr == NULL) {
+ // Invalid target or no queue registered
+ return false;
+ }
+
+ if (data_size > 0 && data == NULL) {
+ // Invalid message structure
+ return false;
+ }
+
+ size_t item_size =
+ ALIGN_UP(sizeof(ipc_queue_item_t) + data_size, IPC_DATA_ALIGNMENT);
+ size_t free_size = queue->size - (queue->wptr - queue->ptr);
+
+ if (item_size > free_size) {
+ // Item is too large
+ return false;
+ }
+
+ ipc_queue_item_t item_hdr = {
+ .free = false,
+ .remote = origin,
+ .fn = fn,
+ .size = data_size,
+ };
+
+ ipc_queue_item_t *item = (ipc_queue_item_t *)queue->wptr;
+ ipc_memcpy(item, &item_hdr, sizeof(item_hdr));
+
+ if (data_size > 0) {
+ ipc_memcpy(item->data, data, data_size);
+ }
+
+ queue->wptr += item_size;
+
+ return true;
+}
+
+static void on_task_created(void *context, systask_id_t task_id) {
+ systask_id_t origin = (systask_id_t)(uintptr_t)context;
+ ipc_queue_t *queue = ipc_queue(task_id, origin);
+ memset(queue, 0, sizeof(ipc_queue_t));
+}
+
+static void on_event_poll(void *context, bool read_awaited,
+ bool write_awaited) {
+ systask_id_t origin = (systask_id_t)(uintptr_t)context;
+
+ UNUSED(write_awaited);
+
+ if (read_awaited) {
+ syshandle_t handle = SYSHANDLE_IPC0 + origin;
+ syshandle_signal_read_ready(handle, NULL);
+ }
+}
+
+static bool on_check_read_ready(void *context, systask_id_t task_id,
+ void *param) {
+ systask_id_t origin = (systask_id_t)(uintptr_t)context;
+
+ UNUSED(param);
+
+ ipc_queue_t *queue = ipc_queue(task_id, origin);
+ return (queue != NULL && queue->rptr < queue->wptr);
+}
+
+static const syshandle_vmt_t g_ipc_handle_vmt = {
+ .task_created = on_task_created,
+ .task_killed = NULL,
+ .check_read_ready = on_check_read_ready,
+ .check_write_ready = NULL,
+ .poll = on_event_poll,
+};
+
+#endif // KERNEL_MODE
diff --git a/core/embed/sys/ipc/ipc_memcpy.h b/core/embed/sys/ipc/ipc_memcpy.h
new file mode 100644
index 00000000..29c60d83
--- /dev/null
+++ b/core/embed/sys/ipc/ipc_memcpy.h
@@ -0,0 +1,33 @@
+/*
+ * 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_types.h>
+
+#include "../ipc_memcpy.h"
+
+/**
+ * Copy memory between two two tasks that may have different MPU settings.
+ *
+ * @param dst Destination pointer
+ * @param src Source pointer
+ * @param size Number of bytes to copy
+ */
+void ipc_memcpy(void *dst, const void *src, size_t size);
diff --git a/core/embed/sys/ipc/stm32u5/ipc_memcpy.c b/core/embed/sys/ipc/stm32u5/ipc_memcpy.c
new file mode 100644
index 00000000..4664c869
--- /dev/null
+++ b/core/embed/sys/ipc/stm32u5/ipc_memcpy.c
@@ -0,0 +1,34 @@
+/*
+ * 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/>.
+ */
+
+#ifdef KERNEL_MODE
+
+#include <trezor_rtl.h>
+
+#include <sys/mpu.h>
+
+#include "../ipc_memcpy.h"
+
+void ipc_memcpy(void *dst, const void *src, size_t size) {
+ mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_DISABLED);
+ memcpy(dst, src, size);
+ mpu_restore(mpu_mode);
+}
+
+#endif // KERNEL_MODE
diff --git a/core/embed/sys/ipc/unix/ipc_memcpy.c b/core/embed/sys/ipc/unix/ipc_memcpy.c
new file mode 100644
index 00000000..6b0d25b1
--- /dev/null
+++ b/core/embed/sys/ipc/unix/ipc_memcpy.c
@@ -0,0 +1,26 @@
+/*
+ * 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 "../ipc_memcpy.h"
+
+void ipc_memcpy(void *dst, const void *src, size_t size) {
+ memcpy(dst, src, size);
+}
diff --git a/core/embed/sys/syscall/inc/sys/syscall_numbers.h b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
index d0102d0b..4e134960 100644
--- a/core/embed/sys/syscall/inc/sys/syscall_numbers.h
+++ b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
@@ -51,6 +51,12 @@ typedef enum {
SYSCALL_SYSLOG_WRITE_CHUNK,
SYSCALL_SYSLOG_SET_FILTER,
+ SYSCALL_IPC_REGISTER,
+ SYSCALL_IPC_UNREGISTER,
+ SYSCALL_IPC_TRY_RECEIVE,
+ SYSCALL_IPC_FREE_MESSAGE,
+ SYSCALL_IPC_SEND,
+
SYSCALL_BOOT_IMAGE_CHECK,
SYSCALL_BOOT_IMAGE_REPLACE,
diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c
index de8d6d1c..eb1dbe4c 100644
--- a/core/embed/sys/syscall/stm32/syscall_dispatch.c
+++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -205,6 +205,38 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
#endif
+#ifdef USE_IPC
+ case SYSCALL_IPC_REGISTER: {
+ systask_id_t origin = (systask_id_t)args[0];
+ void *buffer = (void *)args[1];
+ size_t size = (size_t)args[2];
+ args[0] = ipc_register__verified(origin, buffer, size);
+ } break;
+
+ case SYSCALL_IPC_UNREGISTER: {
+ systask_id_t origin = (systask_id_t)args[0];
+ ipc_unregister(origin);
+ } break;
+
+ case SYSCALL_IPC_TRY_RECEIVE: {
+ ipc_message_t *msg = (ipc_message_t *)args[0];
+ args[0] = ipc_try_receive__verified(msg);
+ } break;
+
+ case SYSCALL_IPC_FREE_MESSAGE: {
+ ipc_message_t *msg = (ipc_message_t *)args[0];
+ ipc_message_free__verified(msg);
+ } break;
+
+ case SYSCALL_IPC_SEND: {
+ systask_id_t remote = (systask_id_t)args[0];
+ uint32_t fn = (uint32_t)args[1];
+ const void *data = (const void *)args[2];
+ size_t data_size = (size_t)args[3];
+ args[0] = ipc_send__verified(remote, fn, data, data_size);
+ } break;
+#endif // USE_IPC
+
case SYSCALL_BOOT_IMAGE_CHECK: {
const boot_image_t *image = (const boot_image_t *)args[0];
args[0] = boot_image_check__verified(image);
diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c
index ae518da6..d4fa5a34 100644
--- a/core/embed/sys/syscall/stm32/syscall_stubs.c
+++ b/core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -103,6 +103,8 @@ ssize_t syshandle_write(syshandle_t handle, const void *data,
#ifdef USE_DBG_CONSOLE
+#include <sys/dbg_console.h>
+
ssize_t dbg_console_read(void *buffer, size_t buffer_size) {
return syscall_invoke2((uint32_t)buffer, buffer_size,
SYSCALL_DBG_CONSOLE_READ);
@@ -139,6 +141,39 @@ bool syslog_set_filter(const char *filter, size_t filter_len) {
#endif
+// =============================================================================
+// ipc.h
+// =============================================================================
+
+#ifdef USE_IPC
+
+#include <sys/ipc.h>
+
+bool ipc_register(systask_id_t origin, void *buffer, size_t size) {
+ return (bool)syscall_invoke3((uint32_t)origin, (uint32_t)buffer, size,
+ SYSCALL_IPC_REGISTER);
+}
+
+void ipc_unregister(systask_id_t origin) {
+ syscall_invoke1((uint32_t)origin, SYSCALL_IPC_UNREGISTER);
+}
+
+bool ipc_try_receive(ipc_message_t *msg) {
+ return (bool)syscall_invoke1((uint32_t)msg, SYSCALL_IPC_TRY_RECEIVE);
+}
+
+void ipc_message_free(ipc_message_t *msg) {
+ syscall_invoke1((uint32_t)msg, SYSCALL_IPC_FREE_MESSAGE);
+}
+
+bool ipc_send(systask_id_t remote, uint32_t fn, const void *data,
+ size_t data_size) {
+ return (bool)syscall_invoke4((uint32_t)remote, fn, (uint32_t)data, data_size,
+ SYSCALL_IPC_SEND);
+}
+
+#endif // USE_IPC
+
// =============================================================================
// boot_image.h
// =============================================================================
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.c b/core/embed/sys/syscall/stm32/syscall_verifiers.c
index 969ee76d..b76c81ef 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.c
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.c
@@ -163,6 +163,65 @@ access_violation:
// ---------------------------------------------------------------------
+#ifdef USE_IPC
+
+bool ipc_register__verified(systask_id_t origin, void *buffer, size_t size) {
+ if (!probe_write_access(buffer, size)) {
+ goto access_violation;
+ }
+
+ return ipc_register(origin, buffer, size);
+
+access_violation:
+ apptask_access_violation();
+ return false;
+}
+
+bool ipc_try_receive__verified(ipc_message_t *msg) {
+ if (!probe_write_access(msg, sizeof(*msg))) {
+ goto access_violation;
+ }
+
+ return ipc_try_receive(msg);
+
+access_violation:
+ apptask_access_violation();
+ return false;
+}
+
+void ipc_message_free__verified(ipc_message_t *msg) {
+ if (!probe_read_access(msg, sizeof(*msg))) {
+ goto access_violation;
+ }
+
+ // We don't not check the block pointed by `msg->data`,
+ // because the msg->data is treated as a "token" and validated
+ // in the ipc_message_free() itself.
+
+ ipc_message_free(msg);
+ return;
+
+access_violation:
+ apptask_access_violation();
+}
+
+bool ipc_send__verified(systask_id_t remote, uint32_t fn, const void *data,
+ size_t data_size) {
+ if (!probe_read_access(data, data_size)) {
+ goto access_violation;
+ }
+
+ return ipc_send(remote, fn, data, data_size);
+
+access_violation:
+ apptask_access_violation();
+ return false;
+}
+
+#endif // USE_IPC
+
+// ---------------------------------------------------------------------
+
bool boot_image_check__verified(const boot_image_t *image) {
if (!probe_read_access(image, sizeof(*image))) {
goto access_violation;
diff --git a/core/embed/sys/syscall/stm32/syscall_verifiers.h b/core/embed/sys/syscall/stm32/syscall_verifiers.h
index 6045bd8a..9104a4e8 100644
--- a/core/embed/sys/syscall/stm32/syscall_verifiers.h
+++ b/core/embed/sys/syscall/stm32/syscall_verifiers.h
@@ -69,7 +69,24 @@ ssize_t syslog_write_chunk__verified(const char *text, size_t text_len,
bool syslog_set_filter__verified(const char *module_name, log_level_t level);
-#endif
+#endif // USE_DBG_CONSOLE
+
+// ---------------------------------------------------------------------
+
+#ifdef USE_IPC
+
+#include <sys/ipc.h>
+
+bool ipc_register__verified(systask_id_t origin, void *buffer, size_t size);
+
+bool ipc_try_receive__verified(ipc_message_t *msg);
+
+void ipc_message_free__verified(ipc_message_t *msg);
+
+bool ipc_send__verified(systask_id_t remote, uint32_t fn, const void *data,
+ size_t data_size);
+
+#endif // USE_IPC
// ---------------------------------------------------------------------
#include <sys/bootutils.h>
diff --git a/core/embed/sys/task/inc/sys/sysevent.h b/core/embed/sys/task/inc/sys/sysevent.h
index 770c64fd..df74bae0 100644
--- a/core/embed/sys/task/inc/sys/sysevent.h
+++ b/core/embed/sys/task/inc/sys/sysevent.h
@@ -35,6 +35,11 @@ typedef enum {
SYSHANDLE_USB,
SYSHANDLE_BLE,
SYSHANDLE_SYSCALL,
+#ifdef USE_IPC
+ SYSHANDLE_IPC0,
+ SYSHANDLE_IPC1,
+ SYSHANDLE_IPC2,
+#endif
SYSHANDLE_COUNT,
} syshandle_t;
diff --git a/core/embed/sys/task/inc/sys/systask.h b/core/embed/sys/task/inc/sys/systask.h
index 1eecc6e7..a3925a3c 100644
--- a/core/embed/sys/task/inc/sys/systask.h
+++ b/core/embed/sys/task/inc/sys/systask.h
@@ -102,8 +102,6 @@ typedef struct {
// The callback may be called from any context, including interrupt context.
typedef void (*systask_error_handler_t)(const systask_postmortem_t* pminfo);
-#ifdef KERNEL_MODE
-
// Maximum number of tasks that can be created
// 1. kernel
// 2. coreapp
@@ -112,6 +110,8 @@ typedef void (*systask_error_handler_t)(const systask_postmortem_t* pminfo);
// Zero-based task ID (up SYSTASK_MAX_TASKS - 1)
typedef uint8_t systask_id_t;
+#ifdef KERNEL_MODE
+
// Task context used by the kernel to save the state of each task
// when switching between them
typedef struct {
diff --git a/core/embed/sys/task/stm32/system.c b/core/embed/sys/task/stm32/system.c
index 8b88a114..2772c740 100644
--- a/core/embed/sys/task/stm32/system.c
+++ b/core/embed/sys/task/stm32/system.c
@@ -37,6 +37,10 @@
#include <sys/dbg_console.h>
#endif
+#ifdef USE_IPC
+#include <sys/ipc.h>
+#endif
+
#ifdef USE_SDRAM
#include <sys/sdram.h>
#endif
@@ -69,6 +73,9 @@ void system_init(systask_error_handler_t error_handler) {
systick_init();
systimer_init();
#ifdef KERNEL
+#ifdef USE_IPC
+ ipc_init();
+#endif
syscall_ipc_init();
#endif
#ifdef USE_DBG_CONSOLE
diff --git a/core/site_scons/models/stm32u5_common.py b/core/site_scons/models/stm32u5_common.py
index 6dd02630..4bee6b09 100644
--- a/core/site_scons/models/stm32u5_common.py
+++ b/core/site_scons/models/stm32u5_common.py
@@ -158,6 +158,14 @@ def stm32u5_common_files(env, features_wanted, defines, sources, paths):
features_wanted += ["system_view"]
defines += ["USE_DBG_CONSOLE_SYSTEM_VIEW"]
+ if "ipc" in features_wanted:
+ sources += [
+ "embed/sys/ipc/ipc.c",
+ "embed/sys/ipc/stm32u5/ipc_memcpy.c",
+ ]
+ defines += [("USE_IPC", "1")]
+ paths += ["embed/sys/ipc/inc"]
+
if "applet" in features_wanted:
sources += ["embed/sys/task/stm32/applet.c"]
sources += ["embed/sys/task/stm32/coreapp.c"]
diff --git a/core/site_scons/models/unix_common.py b/core/site_scons/models/unix_common.py
index 755558cc..a161f466 100644
--- a/core/site_scons/models/unix_common.py
+++ b/core/site_scons/models/unix_common.py
@@ -89,4 +89,12 @@ def unix_common_files(env, features_wanted, defines, sources, paths):
defines += [("USE_DBG_CONSOLE", "1")]
features_available.append("dbg_console")
+ if "ipc" in features_wanted:
+ sources += [
+ "embed/sys/ipc/ipc.c",
+ "embed/sys/ipc/unix/ipc_memcpy.c",
+ ]
+ defines += [("USE_IPC", "1")]
+ paths += ["embed/sys/ipc/inc"]
+
return features_available
Why this scored 15/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.