系统调用用户分发

背景

像 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 的方法。 此接口的用户应确保至少信号跳板代码包含在此区域中。 此外,对于在 vDSO 上实现跳板代码的系统调用,永远不会拦截该跳板。

[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。