Skip to content

userspace: restrict SOF syscalls to the threads that need them - #11408

Open
kv2019i wants to merge 4 commits into
thesofproject:mainfrom
kv2019i:202611-syscall-restrictions
Open

kv2019i wants to merge 4 commits into
thesofproject:mainfrom
kv2019i:202611-syscall-restrictions

Conversation

@kv2019i

@kv2019i kv2019i commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

A set of PRs to add infra to limit syscalls to a subset of threads. This allows to limit attack surface of syscalls. This doesn't remove need to secure the syscalls LL/IPC threads use, but limits the exposure significantly without any functional impact and makes the user-kernel separation more easy to verify and understand.

A standalone test case is added to test the constraints.

@kv2019i

kv2019i commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator Author

Sample test case run on Intel PTL:

Running TESTSUITE userspace_syscall_caller
===================================================================
START - cpu_get_id_any_thread
[    0.000955] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.000955] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.000955] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.000955] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
 PASS - cpu_get_id_any_thread in 0.001 seconds
===================================================================
START - dma_bad_channel_rejected
[    0.000986] <dbg> dma.z_impl_sof_dma_get: dma_get(), dma-probe id = 0
[    0.000986] <inf> dma.z_impl_sof_dma_get: dma_get() ID 0 sref = 1 busy channels 0
[    0.000986] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.000986] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.000986] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.000986] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
[    0.000986] <err> os.z_fatal_error: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0
[    0.000986] <err> os.z_fatal_error: Current thread: 0x401767e8 (unknown)
[    0.000986] <err> coredump.coredump_mem_window_backend_start: #CD:BEGIN#
[    0.000986] <err> coredump.coredump_mem_window_backend_end: #CD:END#
[    0.000986] <err> zephyr.k_sys_fatal_error_handler: Expected fatal error as part of boot test
[    0.000986] <inf> dma.z_impl_sof_dma_put: dma_put(), dma = 0x400da000, sref = 0
 PASS - dma_bad_channel_rejected in 0.002 seconds
===================================================================
START - dma_bad_direction_rejected
[    0.001018] <dbg> dma.z_impl_sof_dma_get: dma_get(), dma-probe id = 0
[    0.001018] <inf> dma.z_impl_sof_dma_get: dma_get() ID 0 sref = 1 busy channels 0
[    0.001018] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.001018] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.001018] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.001018] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
[    0.001018] <err> os.z_fatal_error: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0
[    0.001018] <err> os.z_fatal_error: Current thread: 0x401767e8 (unknown)
[    0.001018] <err> coredump.coredump_mem_window_backend_start: #CD:BEGIN#
[    0.001018] <err> coredump.coredump_mem_window_backend_end: #CD:END#
[    0.001018] <err> zephyr.k_sys_fatal_error_handler: Expected fatal error as part of boot test
[    0.001018] <inf> dma.z_impl_sof_dma_put: dma_put(), dma = 0x400da000, sref = 0
 PASS - dma_bad_direction_rejected in 0.002 seconds
START - forged_vregion_rejected
[    0.001050] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.001050] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.001050] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.001050] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
[    0.001050] <err> os.z_fatal_error: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0
[    0.001050] <err> os.z_fatal_error: Current thread: 0x401767e8 (unknown)
[    0.001050] <err> coredump.coredump_mem_window_backend_start: #CD:BEGIN#
[    0.001050] <err> coredump.coredump_mem_window_backend_end: #CD:END#
[    0.001050] <err> zephyr.k_sys_fatal_error_handler: Expected fatal error as part of boot test
 PASS - forged_vregion_rejected in 0.002 seconds
===================================================================
START - ipc_thread_allowed
[    0.001080] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.001081] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.001081] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.001081] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
 PASS - ipc_thread_allowed in 0.001 seconds
===================================================================
START - ipc_thread_ll_syscall_rejected
[    0.001111] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.001111] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.001111] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.001111] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
[    0.001111] <err> os.z_fatal_error: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0
[    0.001111] <err> os.z_fatal_error: Current thread: 0x401767e8 (unknown)
[    0.001111] <err> coredump.coredump_mem_window_backend_start: #CD:BEGIN#
[    0.001111] <err> coredump.coredump_mem_window_backend_end: #CD:END#
[    0.001113] <err> zephyr.k_sys_fatal_error_handler: Expected fatal error as part of boot test
START - unregistered_thread_rejected
[    0.001143] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72c00 to thread 0x401767e8
[    0.001143] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@72800 to thread 0x401767e8
[    0.001143] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79800 to thread 0x401767e8
[    0.001143] <dbg> userspace_helper.user_grant_dma_access_all: Granted DMA device access: dma@79400 to thread 0x401767e8
[    0.001143] <err> os.z_fatal_error: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0
[    0.001143] <err> os.z_fatal_error: Current thread: 0x401767e8 (unknown)
[    0.001143] <err> coredump.coredump_mem_window_backend_start: #CD:BEGIN#
[    0.001143] <err> coredump.coredump_mem_window_backend_end: #CD:END#
[    0.001143] <err> zephyr.k_sys_fatal_error_handler: Expected fatal error as part of boot test
 PASS - unregistered_thread_rejected in 0.002 seconds
===================================================================
TESTSUITE userspace_syscall_caller succeeded

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical registration and caller-check defects can break normal IPC, DP notifications, and module cleanup, while a user-writable thread handle can grant LL syscall privileges to an unintended thread.

5 open issues
What changed in this PR

This PR narrows SOF’s userspace syscall access by distinguishing infrastructure threads from module threads, reducing the exposed kernel interface.

Changes:

  • Adds a default-enabled caller check backed by a kernel-only thread registry.
  • Applies role restrictions to syscall validators and registers LL scheduler threads.
  • Adds standalone security tests, adapts existing DMA tests, and documents allowed callers.
File Description
zephyr/​test/​userspace/​test_syscall_caller.c Adds caller-role and invalid-argument tests.
zephyr/​test/​userspace/​test_intel_ssp_dai.c Registers the test thread with the IPC role.
zephyr/​test/​userspace/​test_intel_hda_dma.c Registers the test thread with the IPC role.
zephyr/​test/​CMakeLists.txt Enables the new standalone test suite.
zephyr/​syscall/​vregion.c Restricts virtual-region syscalls to infrastructure threads.
zephyr/​syscall/​sof_dma.c Restricts DMA syscalls to infrastructure threads.
zephyr/​syscall/​dai.c Restricts DAI device lookup.
zephyr/​syscall/​caller.c Implements the kernel-only caller registry.
zephyr/​syscall/​alloc.c Restricts heap allocation and freeing.
zephyr/​Kconfig Adds the default-enabled caller-check option.
zephyr/​CMakeLists.txt Conditionally builds the caller registry.
src/​schedule/​zephyr_ll.c Restricts LL task semaphore management.
src/​schedule/​zephyr_dp_schedule.c Limits scheduler ticks to LL threads.
src/​schedule/​zephyr_dp_schedule_application.c Restricts DP task cleanup.
src/​schedule/​zephyr_domain.c Registers and unregisters LL threads.
src/​library_manager/​lib_manager.c Uses the registry to authorize module unloading.
src/​ipc/​ipc4/​notification.c Restricts resource notifications.
src/​ipc/​ipc4/​handler-kernel.c Adds role checks to IPC handlers.
src/​ipc/​ipc-common.c Restricts message sending and queue removal.
src/​include/​sof/​userspace/​syscall_caller.h Defines roles, registration APIs, and check macros.
src/​include/​sof/​userspace/​README.md Documents caller permissions and validator requirements.
src/​debug/​debug_stream/​debug_stream_slot.c Restricts debug record submission.
src/​audio/​kpb.c Restricts KPB notifier initialization.

Review effort: Balanced

Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc4/handler-kernel.c Resolved
Comment thread src/ipc/ipc4/notification.c Outdated
uint32_t resource_type, void *data,
uint32_t data_size)
{
SOF_SYSCALL_CALLER_CHECK(SOF_SYSUSER_INFRA);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good catch, this is valid. Fixed in V2.

Comment thread src/schedule/zephyr_domain.c Resolved
@intel-sofci

Copy link
Copy Markdown

PR 11408: test results

Run date: 2026-11-06 11:27 UTC

Tested commit: 5baf828f61a14b13c1a870a424728247e0f30ca0

mtl pass rate lnl pass rate ptl pass rate wcl pass rate nvl pass rate

Zephyr lets any user thread invoke any system call. SOF user threads
have different trust levels: the LL scheduler and IPC user threads are
SOF infrastructure, while DP threads run module code that can come from
a third party.

Add a registry in kernel-only memory that records the LL and IPC user
thread of each core, and sof_syscall_caller_allowed() for validators to
check the calling thread against a set of roles. Register the threads
where they are created, before they start, and clear the LL entry before
the thread object is freed.

The registry is enabled with the new SOF_USERSPACE_SYSCALL_CALLER_CHECK
option, default y with SOF_USERSPACE_LL. No validator uses it yet.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Use the caller registry in the validators of SOF system calls, so DP
module threads can only reach the syscalls needed to implement the
module interface, including sending notifications:

- any user thread: cpu_get_id, the mod_* module syscalls, ipc_msg_send,
  ipc_msg_list_remove and send_resource_notif
- IPC user thread only: ipc_compound_pre_start, ipc_compound_post_start,
  ipc_wait_for_compound_msg, lib_manager_free_module
- LL user thread only: scheduler_dp_ll_tick
- LL or IPC user thread: all other SOF syscalls

DP modules send notifications with mod_ipc_msg_init() and
ipc_msg_send(), free them with mod_ipc_msg_free() from their .free()
callback, and dp_task_run() reports processing errors with
send_resource_notif(), so these stay open to all user threads.

lib_manager_free_module() previously identified the IPC thread through
ipc_user->thread[], which user threads can write. It now uses the
registry, keeping the old check only if the option is disabled.

The HDA DMA and SSP DAI userspace tests call DMA syscalls from their own
user thread, so register that thread for the duration of the test.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add the missing kpb_notifier_init and mod_data_blob_set entries to the
syscall map, document which threads may call which syscalls, and extend
the rules for adding syscalls with the caller check, single fetch of
user data and not following user-writable pointers in the kernel.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add standalone boot tests that check the caller restriction, rejection
of a forged vregion handle, and the DMA channel range and transfer
direction checks. Cases that need features the target lacks are
skipped.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
@kv2019i
kv2019i force-pushed the 202611-syscall-restrictions branch from 5baf828 to c171548 Compare November 6, 2026 16:49
@kv2019i

kv2019i commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator Author

V2 pushed:

  • fixed code review comments so far. DP modules again allowed to send notifications (we may return to this later)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants