系统调用用户分发¶
背景¶
诸如 Wine 之类的兼容层需要一种有效的方式来模拟进程中只有一部分的系统调用 - 具有不兼容代码的部分 - 同时能够以很高的性能执行本机系统调用,而不会对进程的本机部分造成性能损失。 Seccomp 在此任务上有所不足,因为它对基于内存区域有效过滤系统调用的支持有限,并且不支持删除过滤器。 因此,需要一种新的机制。
系统调用用户分发将系统调用分发器地址的过滤带回用户空间。应用程序可以控制一个翻转开关,指示进程的当前个性。然后,多重个性应用程序可以在跨越兼容层 API 边界时翻转开关,而无需调用内核,以启用/禁用系统调用重定向并通过 SIGSYS 直接执行系统调用(禁用)或将它们发送到用户空间进行模拟。
此设计的目标是提供非常快速的兼容层边界交叉,这可以通过每次兼容层执行时都不执行系统调用来更改个性来实现。相反,暴露给内核的用户空间内存区域指示当前的个性,并且应用程序只需修改该变量即可配置该机制。
在大多数架构(如 x86)上,处理信号的成本相对较高,但至少对于 Wine 而言,由本机 Windows 代码发出的系统调用目前尚不存在性能问题,因为它们非常罕见,至少对于现代游戏应用程序而言。
由于此机制旨在捕获由非本机应用程序发出的系统调用,因此它必须在调用 ABI 对于 Linux 完全意外的系统调用上起作用。因此,系统调用用户分发不依赖于任何系统调用 ABI 来进行过滤。它仅使用系统调用分发器地址和用户空间密钥。
由于这些拦截的系统调用的 ABI 对 Linux 来说是未知的,因此这些系统调用无法通过 ptrace 或系统调用跟踪点进行检测。
接口¶
线程可以通过执行以下 prctl 在受支持的内核上设置此机制
prctl(PR_SET_SYSCALL_USER_DISPATCH, <op>, <offset>, <length>, [selector])
<op> 是 PR_SYS_DISPATCH_ON 或 PR_SYS_DISPATCH_OFF,用于全局启用和禁用该线程的机制。使用 PR_SYS_DISPATCH_OFF 时,其他字段必须为零。
[<offset>, <offset>+<length>) 划定一个内存区域间隔,无论用户空间选择器如何,系统调用总是从该内存区域直接执行。这为 C 库提供了一条快速通道,其中包括本机代码应用程序中最常见的系统调用分发器,并且还提供了一种方法,使信号处理程序可以返回而不会在 (rt_)sigreturn 上触发嵌套的 SIGSYS。此接口的用户应确保至少信号 trampoline 代码包含在此区域中。此外,对于在 vDSO 上实现 trampoline 代码的系统调用,该 trampoline 永远不会被拦截。
[selector] 是指向进程内存区域中 char 大小的区域的指针,它提供了一种快速启用/禁用线程范围系统调用重定向的方法,而无需直接调用内核。 selector 可以设置为 SYSCALL_DISPATCH_FILTER_ALLOW 或 SYSCALL_DISPATCH_FILTER_BLOCK。任何其他值都应使用 SIGSYS 终止程序。
此外,可以通过 PTRACE_(GET|SET)_SYSCALL_USER_DISPATCH_CONFIG ptrace 请求来窥视和设置任务的系统调用用户分发配置。这对于检查点/重启软件非常有用。
安全注意事项¶
系统调用用户分发为兼容层提供了快速捕获由应用程序的非本机部分发出的系统调用的功能,同时不影响进程的 Linux 本机区域。它不是沙盒系统调用的机制,也不应将其视为安全机制,因为恶意应用程序可以通过跳转到允许的分发器区域,然后在执行系统调用之前,或者发现地址并修改选择器值来轻松破坏该机制。如果用例需要任何类型的安全沙盒,则应使用 Seccomp。
现有进程的任何 fork 或 exec 都会将机制重置为 PR_SYS_DISPATCH_OFF。