资讯动态

Linux 内核 Syscall User Dispatch(SUD)机制解析:prctl 接口、实现原理与 SELFTESTS 实战

发布时间:2026/9/11 1:07:52 来源:尧图企业网站定制
Linux 内核 Syscall User DispatchSUD机制解析prctl 接口、实现原理与 SELFTESTS 实战【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxSyscall User DispatchSUD是 Linux 内核提供的一项把系统调用分发器syscall dispatcher的过滤能力交还给用户态的机制专为 Wine 等兼容层设计进程内非原生代码段发出的系统调用被重定向到用户态模拟器通过 SIGSYS 信号而原生代码段仍可直接执行系统调用几乎零开销。读完本文你将掌握prctl(PR_SET_SYSCALL_USER_DISPATCH, ...)的完整参数语义、PR_SYS_DISPATCH_EXCLUSIVE_ON与PR_SYS_DISPATCH_INCLUSIVE_ON两种区间模式的选择方法、selecter 翻转开关的正确用法以及如何通过内核源码与自带的 selftests 验证和理解这套机制。背景为什么 Seccomp 满足不了兼容层的需求像 Wine 这样的兼容层需要在同一进程内运行两种人格personality的代码一部分是面向 Windows ABI 的非原生代码其系统调用需要被拦截并模拟另一部分是原生 Linux 代码如 C 库、原生应用逻辑其系统调用应当直接进入内核执行不能有性能损耗。Seccomp 在这个场景下力不从心原因有二见 Documentation/admin-guide/syscall-user-dispatch.rstSeccomp 对基于内存区域memory regions来高效过滤系统调用的支持非常有限Seccomp 不支持在运行时移除过滤器。因此内核引入了 Syscall User Dispatch它不依赖任何系统调用 ABI因为被拦截的可能是 Linux 完全无法理解的调用约定只依赖系统调用分发指令的地址syscall dispatcher address和用户态开关变量userspace key两个要素来做过滤。一个重要的设计目标是极快地跨越兼容层边界进程不需要为了切换人格而每次执行一次真正的系统调用而是由内核暴露一块用户态内存区域应用程序直接修改该变量即可启用/禁用重定向。文档同时指出虽然 x86 等架构上处理信号的代价较高但对 Wine 而言现代游戏类应用中原生 Windows 代码发出的系统调用相当稀少因此信号路径的开销可以接受。接口prctl(PR_SET_SYSCALL_USER_DISPATCH)一个线程可以在支持该机制的内核上通过如下 prctl 调用完成配置prctl(PR_SET_SYSCALL_USER_DISPATCH, op, offset, length, [selector]);对应内核侧的入口位于 kernel/sys.ccase PR_SET_SYSCALL_USER_DISPATCH: error set_syscall_user_dispatch(arg2, arg3, arg4, (char __user *) arg5); break;参数含义与常量定义见 include/uapi/linux/prctl.h参数含义opPR_SYS_DISPATCH_EXCLUSIVE_ON值 1、PR_SYS_DISPATCH_INCLUSIVE_ON值 2或PR_SYS_DISPATCH_OFF值 0。其中PR_SYS_DISPATCH_ON是PR_SYS_DISPATCH_EXCLUSIVE_ON的向后兼容别名offset/length描述一个内存区间[offset, offsetlength)selector指向进程内存中一个char大小区域的指针用于线程级快速切换重定向开关PR_SYS_DISPATCH_OFF全局关闭该线程的机制。使用PR_SYS_DISPATCH_OFF时其余字段必须全部为 0否则返回-EINVAL见 kernel/entry/syscall_user_dispatch.c。PR_SYS_DISPATCH_EXCLUSIVE_ON白名单快速通道[offset, offsetlength)界定一个始终直接执行系统调用的内存区间与用户态 selector 的值无关。这为 C 库提供了快速通道——原生代码中最常见的系统调用分发器就在 C 库里同时它也给信号处理器提供了安全返回的途径(rt_)sigreturn不会被再次拦截而产生嵌套 SIGSYS。文档特别提醒使用该接口的用户应确保至少把信号跳板signal trampoline代码包含在该区间内。另外凡是在 vDSO 上实现跳板代码的系统调用其跳板永远不会被拦截内核实现见 kernel/entry/syscall_user_dispatch.c 中的arch_syscall_is_vdso_sigreturn()检查。PR_SYS_DISPATCH_INCLUSIVE_ON黑名单区间[offset, offsetlength)界定一个依据用户态 selector 进行分发的内存区间区间之外的系统调用一律直接执行。内核实现时会把区间取反存储见 kernel/entry/syscall_user_dispatch.coffset offset len; len -len;从而复用与独占模式相同的区间内命中则放行检查逻辑该检查天然支持地址回绕。selector 开关变量[selector]指向进程地址空间内一个字节大小的区域取以下两个值之一定义见 include/uapi/linux/prctl.hSYSCALL_DISPATCH_FILTER_ALLOW0放行系统调用直接执行SYSCALL_DISPATCH_FILTER_BLOCK1拦截系统调用重定向到用户态处理。任何其他值都会以 SIGSYS 终止程序不可捕获见下文实现细节。切换 selector 只需一次普通内存写无需进入内核这正是快速跨越兼容层边界的关键。内核实现从入口检查到 SIGSYS 触发核心实现位于 kernel/entry/syscall_user_dispatch.c函数syscall_user_dispatch()在每次系统调用进入时被调用执行以下判定链第 36-71 行快速路径instruction_pointer(regs) - sd-offset sd-len命中放行区间则直接返回false正常执行系统调用vDSO sigreturn 豁免若当前是 vDSO 上的 sigreturn 跳板则放行读取 selector若selector非空用__get_user读取状态字节access_ok已在 prctl 时校验过一次判断状态ALLOW直接放行既非ALLOW也非BLOCK的非法值调用force_exit_sig(SIGSYS)直接终止进程触发分发设置on_dispatch标志、回滚系统调用syscall_rollback并通过trigger_sigsys()发送 SIGSYS——si_code为SYS_USER_DISPATCH同时携带si_syscall、si_arch、si_call_addr等现场信息供用户态模拟器恢复执行。info.si_signo SIGSYS; info.si_code SYS_USER_DISPATCH; info.si_call_addr (void __user *)KSTK_EIP(current); info.si_arch syscall_get_arch(current); info.si_syscall syscall_get_nr(current, regs);由于被拦截系统调用的 ABI 对 Linux 而言是未知的这些系统调用无法通过 ptrace 或系统调用 tracepoints 进行插桩见原文档。set_syscall_user_dispatch()的参数校验逻辑kernel/entry/syscall_user_dispatch.c值得留意PR_SYS_DISPATCH_OFFoffset || len || selector任一非零即返回-EINVALPR_SYS_DISPATCH_EXCLUSIVE_ON仅做基本健全性检查——拒绝区间回绕offset len offset和 0 大小区间注释明确说明只要用户能从某地址发起系统调用该地址显然就是有效的因此不做进一步地址验证PR_SYS_DISPATCH_INCLUSIVE_ON拒绝len 0或区间回绕selector 必须通过access_ok(untagged_addr(selector), sizeof(*selector))否则返回-EFAULT对启用 memory tagging 的架构会先做地址去标签处理确保 tracer 能为 tracee 设置 selector。配置生效后通过set_task_syscall_work(task, SYSCALL_USER_DISPATCH)/clear_task_syscall_work(...)控制任务级 syscall work 位。sysctl 运行时开关是否允许启用该机制由 sysctl 控制kernel.syscall_user_dispatch布尔值默认允许。实现见 kernel/entry/syscall_user_dispatch.c全局变量syscall_user_dispatch_allowed初始为true通过register_sysctl_init(kernel, ...)注册。在内核侧只要武装非PR_SYS_DISPATCH_OFF且该值为假prctl 会返回-EPERM解除disarm始终允许。这一开关可在系统层面关闭 SUD 能力供需要收紧攻击面的部署使用。通过 ptrace 读取/写入配置Checkpoint/Restart 场景任务的 SUD 配置可以通过两个 ptrace 请求窥视与修改定义见 include/uapi/linux/ptrace.h#define PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG 0x4210 #define PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG 0x4211配套的数据结构struct ptrace_sud_config包含modePR_SYS_DISPATCH_ON/PR_SYS_DISPATCH_OFF、selectortracee 视角的 SUD selector 虚拟地址、offset、len字段。内核实现位于 kernel/entry/syscall_user_dispatch.csyscall_user_dispatch_get_config()与syscall_user_dispatch_set_config()要求size精确等于sizeof(struct ptrace_sud_config)否则返回-EINVAL设置路径最终复用同一个task_set_syscall_user_dispatch()。这对 checkpoint/restartC/R类软件保存和恢复进程的 SUD 状态非常有用——对应的 selftest 见 tools/testing/selftests/ptrace/get_set_sud.c。安全边界不是沙箱别当安全机制用原文档用专门一节强调SUD 是为兼容层快速捕获非原生部分系统调用而设计的它不是系统调用沙箱也不应被视为安全机制。原因很直白——恶意程序可以轻易地跳转到放行区间内的分发器地址后再执行系统调用找到并篡改 selector 变量。如果用例需要任何形式的安全沙箱应当使用 Seccomp。此外对现有进程执行 fork 或 exec 都会把机制重置为PR_SYS_DISPATCH_OFF这是内核侧任务结构按copy_creds/exec语义自然清零的结果因此该配置只作用于单线程生命周期内的当前进程映像。实战从 selftests 验证到最小示例仓库自带完整的 SUD 自测集需CONFIG_SYSCALL_USER_DISPATCHy见 tools/testing/selftests/syscall_user_dispatch/config目录位于tools/testing/selftests/syscall_user_dispatch/覆盖了文档描述的全部核心行为sud_test.cdispatch_trigger_sigsysselector 置BLOCK后执行sysinfo()验证 SIGSYS 被触发bad_prctl_param非法 op、PR_SYS_DISPATCH_OFF携带非零参数、区间回绕、非法 selector(void *)-1期望EFAULT等均返回EINVAL/EFAULTdispatch_and_return通过全局 selector 与MAGIC_SYSCALL_1一个不存在的系统调用号__NR_syscalls 1验证放行时直接执行、阻塞时经 SIGSYS 模拟后返回并断言si_code SYS_USER_DISPATCH、si_errno 0bad_selector把 selector 置为-1后执行系统调用即使进程已注册 SIGSYS 处理器信号依然不可捕获、进程直接终止disable_dispatchPR_SYS_DISPATCH_OFF之后 selector 不再有任何作用direct_dispatch_range/dispatch_range验证区间边界语义——命中放行区间则旁路分发区间外则按 selector 分发。最小可用示例拦截不存在的系统调用综合文档与 selftests一个最简的 SUD 使用范式如下逻辑对齐 sud_test.c 中的dispatch_and_return测试#define _GNU_SOURCE #include sys/prctl.h #include sys/syscall.h #include sys/sysinfo.h #include signal.h #ifndef PR_SET_SYSCALL_USER_DISPATCH #define PR_SET_SYSCALL_USER_DISPATCH 59 #endif #define PR_SYS_DISPATCH_EXCLUSIVE_ON 1 #define SYSCALL_DISPATCH_FILTER_ALLOW 0 #define SYSCALL_DISPATCH_FILTER_BLOCK 1 static char selector; /* 用户态翻转开关 */ static int nr_emulated; static void handle_sigsys(int sig, siginfo_t *info, void *ucontext) { nr_emulated; selector SYSCALL_DISPATCH_FILTER_ALLOW; /* 模拟完成放行 */ /* 在此执行用户态系统调用模拟…… */ } int main(void) { struct sigaction act { .sa_sigaction handle_sigsys, .sa_flags SA_SIGINFO }; sigaction(SIGSYS, act, NULL); selector SYSCALL_DISPATCH_FILTER_ALLOW; prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_EXCLUSIVE_ON, 0, 0, selector); /* 放行区间为空全部按 selector 分发 */ selector SYSCALL_DISPATCH_FILTER_BLOCK; syscall(__NR_syscalls 1); /* 不存在的系统调用号 → 被拦截模拟 */ return 0; }注意示例中放行区间传(0, 0)等价于无放行区间真实兼容层必须把 C 库分发器与信号跳板地址区间填入offset/length否则从 SIGSYS 处理器返回时连sigreturn都会再次被拦截。性能评估内置 benchmarksud_benchmark.c 提供了一个基准程序先校准基准 syscall 耗时sysinfo()循环再启用 SUD 拦截并统计trapped_call_count与native_call_count最后输出Interception overhead: x.x% (yns)。它通过内联汇编在 x86_64 上构造了syscall_dispatcher_start/end区间标记i386 依赖 vDSO 跳板TEST_BLOCKED_RETURN仅在这两个架构上启用并验证返回时 selector 保持 BLOCK这一在阻塞模式下安全返回的关键路径。该基准直观印证了文档原生部分零开销、非原生部分经信号模拟的设计权衡——你可以按文档与仓库tools/testing/selftests/的通用构建方式编译运行这两个程序做实测。小结适用场景与边界速查关注点结论适用场景兼容层Wine 等中按代码区间快速捕获非原生代码的系统调用核心 APIprctl(PR_SET_SYSCALL_USER_DISPATCH, op, offset, len, selector)快速路径PR_SYS_DISPATCH_EXCLUSIVE_ON放行区间 selector 置ALLOW均不进入内核即可直通拦截路径selector 置BLOCK→ 内核回滚系统调用 → 投递SIGSYSsi_code SYS_USER_DISPATCH配置读取PTRACE_(GET|SET)_SYSCALL_USER_DISPATCH_CONFIG用于 checkpoint/restart全局开关sysctlkernel.syscall_user_dispatch默认开禁用后返回-EPERM生命周期fork/exec 后重置为PR_SYS_DISPATCH_OFF安全定位非沙箱、非安全机制沙箱需求请使用 Seccomp验证途径tools/testing/selftests/syscall_user_dispatch/自测集与基准程序Syscall User Dispatch 以一个字节的用户态开关 一段放行区间的极简抽象解决了兼容层系统调用分发的性能痛点其设计取舍不依赖 ABI、不插桩 ptrace/tracepoint、明确的非安全定位在 Documentation/admin-guide/syscall-user-dispatch.rst 中有完整交代并可由上述源码与测试逐一印证。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价