目录

1) 引言

scx 项目提供了一系列用 Rust 和 C 实现的动态可加载的自定义调度器,它们利用了 Linux 内核的 sched_ext 功能。一个可选的 D-Bus 服务 scx_loader 提供了一个系统上所有用户都可以访问的接口,允许加载和配置 scx 提供的调度器。此 D-Bus 服务在 scx v1.0.17 及之前的版本中存在。为了响应此报告,scx_loader 已被移至一个专门的仓库

一位 SUSE 同事将 scx 打包以添加到 openSUSE Tumbleweed 中,其中包含的 D-Bus 服务需要我们团队的审查。审查显示,D-Bus 服务以完整的 root 权限运行,并且缺少身份验证层,从而允许任何用户几乎随意更改系统的调度属性,导致拒绝服务和其他攻击向量。

上游拒绝了此报告的协调披露,并要求我们立即公开处理。在随后的讨论中,上游拒绝了我们报告的部分内容,并且没有提出明确的修复问题的途径,这就是为什么目前没有可用的错误修复。

第2节提供了 scx_loader D-Bus 服务及其缺乏身份验证的概述。第3节探讨了可能受非特权客户端影响的问题命令行参数。第4节探讨了使用 scx_loader API 实现本地 root 漏洞利用的尝试。第5节列出了受影响的 Linux 发行版。第6节讨论了修复此报告中发现的问题的可能方法。第7节探讨了上游为修复这些问题所做的努力。

本报告基于 scx 的1.0.16 版本

2) 未经身份验证的 scx_loader D-Bus 服务概述

scx_loader D-Bus 服务是用 Rust 实现的,并在系统总线上提供了一个完全未经身份验证的 D-Bus 接口。上游仓库包含配置文件和文档,宣传此服务适合通过 D-Bus 请求自动启动。因此,系统中的任意用户(包括低特权服务用户甚至 nobody)都被允许无限制地使用该服务。

服务的接口提供启动、停止或切换多个 scx 调度器的功能。启动和切换方法还允许指定任意参数列表,这些参数将直接传递给实现调度器的二进制文件。

每个调度器都在一个专门的二进制文件中实现,例如 bpfland 调度器的路径是 /usr/bin/scx_bpfland。并非所有 scx 的调度器都可以通过此接口访问。在审查版本中,scx_loader 支持的调度器列表是

scx_bpfland scx_cosmos scx_flash scx_lavd
scx_p2dq scx_tickless scx_rustland scx_rusty

我们认为,或多或少地随意调整系统的调度行为已经构成了本地拒绝服务 (DoS) 攻击向量,甚至可能导致整个系统锁定。我们没有深入研究可能实现这一目标的具体参数集,但考虑到通过 D-Bus 接口提供的调度器及其参数范围,这似乎很有可能。

3) 向调度器传递任意参数

能够将任意命令行参数传递给任何受支持的调度器二进制文件,大大增加了 D-Bus 接口的攻击面。这使得一些具体的攻击成为可能,特别是当相关调度器接受文件路径作为输入时。除了影响调度器行为的参数外,所有调度器都提供通用的“Libbpf 选项”,其中以下四个选项在此上下文中突出

--pin-root-path <PIN_ROOT_PATH>      Maps that set the 'pinning' attribute in their definition will have
                                     their pin_path attribute set to a file in this directory, and be
                                     auto-pinned to that path on load; defaults to "/sys/fs/bpf"
--kconfig <KCONFIG>                  Additional kernel config content that augments and overrides system
                                     Kconfig for CONFIG_xxx externs
--btf-custom-path <BTF_CUSTOM_PATH>  Path to the custom BTF to be used for BPF CO-RE relocations. This custom
                                     BTF completely replaces the use of vmlinux BTF for the purpose of CO-RE
                                     relocations. NOTE: any other BPF feature (e.g., fentry/fexit programs,
                                     struct_ops, etc) will need actual kernel BTF at /sys/kernel/btf/vmlinux
--bpf-token-path <BPF_TOKEN_PATH>    Path to BPF FS mount point to derive BPF token from. Created BPF token
                                     will be used for all bpf() syscall operations that accept BPF token
                                     (e.g., map creation, BTF and program loads, etc) automatically within
                                     instantiated BPF object. If bpf_token_path is not specified, libbpf will
                                     consult LIBBPF_BPF_TOKEN_PATH environment variable. If set, it will be
                                     taken as a value of bpf_token_path option and will force libbpf to
                                     either create BPF token from provided custom BPF FS path, or will
                                     disable implicit BPF token creation, if envvar value is an empty string.
                                     bpf_token_path overrides LIBBPF_BPF_TOKEN_PATH, if both are set at the
                                     same time. Setting bpf_token_path option to empty string disables
                                     libbpf's automatic attempt to create BPF token from default BPF FS mount
                                     point (/sys/fs/bpf), in case this default behavior is undesirable

libbpf 是用于 BPF 程序的 Linux 源代码树中找到的用户空间支持库。以下小节将详细介绍传递给此库的每个攻击者控制的路径。

--pin-root-path 选项

--pin-root-path 选项可能导致 libbpf 在 bfp_object__pin_programs() 中创建此路径的父目录。然而,我们不完全确定触发此逻辑的条件,以及这些条件是否在 scx_loader D-Bus API 的上下文中由非特权调用者控制。

--kconfig 选项

libbpf_clap_opts.rs 第 91 行中,--kconfig 路径中找到的文件被完全读入内存。这使得一些攻击向量成为可能

  • 指向设备文件,例如 /dev/zero,会导致所选调度器二进制文件内存不足。
  • 指向私有文件,例如 /etc/shadow,会导致调度器二进制文件读取私有数据。然而,我们没有找到将此数据泄露给非特权 D-Bus 调用者的方法。这种技术仍然允许在通常非特权用户无法访问的位置执行文件存在性测试。
  • 指向 FIFO 命名管道将无限期地阻塞调度器二进制文件,破坏 D-Bus 服务。此外,通过向此类 PIPE 提供数据,几乎所有内存都可能被耗尽,使系统处于低内存状态,并可能导致内核的 OOM killer 定位无关进程。
  • 通过指向调用者控制的常规文件,可以将精心制作的 KConfig 信息传递给 libbpf。然而,其影响似乎很小。

以下命令行是一个示例复现器,它将导致 scx_bpfland 进程消耗所有系统内存,直到它被内核杀死

user$ gdbus call -y -d org.scx.Loader -o /org/scx/Loader \
    -m org.scx.Loader.SwitchSchedulerWithArgs scx_bpfland \
    '["--kconfig", "/dev/zero"]'

--btf-custom-path 选项

--btf-custom-path 选项提供了与上面讨论的 --kconfig 选项类似的攻击向量。此外,可以通过此路径将精心制作的二进制符号信息馈送给调度器,这些信息将由 libbpf 中的 btf_parse_raw()btf_parse_elf() 处理。这可能导致调度器/内核的完整性受到损害,由于我们在此底层领域缺乏专业知识,并且不想投入超出分析所需的时间,我们无法完全判断其影响。

--bpf-token-path 选项

如果 --bpf-token-path 指向一个目录,它将被 libbpf 打开,并且文件描述符将像这样传递给 bpf 系统调用

bpf(BPF_TOKEN_CREATE, {token_create={flags=0, bpffs_fd=20}}, 8) = -1 EINVAL (Invalid argument)

然而,这似乎没有任何作用,因为如果调用者位于初始用户命名空间中(特权 D-Bus 服务总是如此),内核代码会拒绝调用者。如果调度器“启动”操作的行为根据输入显示可观察到的差异,该路径也许可以作为信息泄露来测试文件存在性和类型。

4) 本地 root 漏洞利用的边缘

通过对许多不同调度器二进制文件的命令行参数进行如此多的控制,这些二进制文件提供了广泛的选项,我们最初认为实现完整的本地 root 漏洞利用不会很困难。然而,我们努力尝试,但到目前为止尚未找到任何有效的攻击向量。然而,可能我们在上一节中讨论的关于攻击者控制的输入文件的底层 BPF 处理方面遗漏了一些东西。

scx_loader 仅因为其接口只能访问可用 scx 调度器二进制文件的一个子集而免于简单的本地 root 漏洞利用。scx_chaos 调度器,它不在 D-Bus 服务提供的调度器之列,支持一个位置命令行参数,指的是“在混沌调度器下运行的程序”。如果此调度器可以通过 D-Bus 访问,那么非特权用户可以导致用户控制的程序以完整的 root 权限执行,从而导致任意代码执行。

与上游的讨论表明,将 scx_chaos 等调度器从 D-Bus 接口中排除并非出于安全考虑,而是出于功能限制,因为某些调度器并非在所有上下文中都受支持,或者尚未稳定。

5) 受影响的 Linux 发行版

根据我们的调查和与上游的沟通,似乎只有 Arch Linux 在其默认安装的 scx 中受到此问题的影响。Gentoo Linux 带有 scx 的 ebuild,但由于某种原因,scx_loader 没有集成到 init 系统中,并且 D-Bus 自动启动配置文件也缺失。因此,只有管理员手动调用服务时才会受到影响。

否则,我们没有在当前的 Fedora Linux、Ubuntu LTS 或 Debian Linux 上找到 scx_loader 的打包。由于此次审查的结果,我们从未允许 D-Bus 服务进入 openSUSE,因此它也不受影响。

6) 建议的修复措施

在 D-Bus 级别限制对某个组的访问

对于此问题最糟糕方面的一个快速修复方法是限制 D-Bus 配置在 org.scx.Loader.conf 中,只允许专用组(如 scx)的成员访问接口。这至少可以防止随机的非特权用户滥用 API。

我们提供了一个可供下载的补丁,它正是这样做的。

使用 Polkit 进行身份验证

通过集成 Polkit 身份验证,可以将此接口的使用限制为物理存在的交互式用户。即使在这种情况下,我们建议通过 Polkit 的 auth_admin_keep 设置,将完整的 API 访问权限限制为可以作为管理员进行身份验证的用户。只读操作仍然可以在未经身份验证的情况下允许。

使 API 更加健壮

scx.Loader D-Bus 服务提供的各个方法不应允许执行超出预期范围的操作,即使调用者已通过前几节中概述的某种形式进行身份验证。

为此,危险的调度器参数要么应该被拒绝(例如,通过强制执行允许参数的白名单),要么应该被验证(例如,通过确定所提供的路径是否仅受 root 控制以及类似检查)。

关于输入文件,客户端理想情况下根本不应传递路径名,而是发送文件描述符,以避免意外和在特权 D-Bus 服务中验证输入路径的负担。

使用 systemd 沙箱

scx_loader 的 systemd 服务可以利用 systemd 提供的各种强化选项(例如 ProtectSystem=full),只要这些选项不干扰服务的功能。这将防止在第一道防线失败时更危险的攻击向量成功。

7) 上游错误修复缺失

上游对我们在 GitHub 问题中提供的报告反应不情愿,拒绝了我们评估的部分内容。一项基于 AI 生成代码引入 Polkit 身份验证层的尝试很快被放弃,上游转而将 scx_loader 服务拆分到一个新仓库中,以将其与 scx 核心项目分开。我们最初的 GitHub 问题已被关闭,我们克隆了它到新仓库中以跟踪问题。

scx_loader 的下游集成商可以通过应用我们在建议的修复措施部分提供的补丁,将对 D-Bus 服务的访问限制为 scx 组的成员。这样,对问题 API 的访问就变为选择加入,并限制为实际打算使用此服务的特权用户。

8) CVE 分配

我们向上游建议至少分配一个累积 CVE,以涵盖导致本地 DoS、潜在信息泄露和完整性违反的未经身份验证的 D-Bus 接口方面。我们提出从 SUSE 池中分配一个 CVE,以简化流程。

上游没有对此作出回应,也没有明确确认我们提出的问题,而是拒绝了我们报告的某些内容。因此,目前没有可用的 CVE 分配。

9) 时间线

2025-09-30 我们通过电子邮件联系了其中一位上游开发人员,询问该项目的安全联系人,因为仓库中没有记录。
2025-09-30 上游开发人员同意与项目中的另一位开发人员共同处理报告。
2025-09-30 我们与两位开发人员分享了一份详细报告。
2025-10-02 在分析报告后,上游开发人员建议创建一个公开的 GitHub 问题,我们照做了
2025-10-03 一位上游开发人员回复了该问题,拒绝了我们报告的各个部分。
2025-10-28 稍后,我们提供了简短的回复,指出这些拒绝似乎忽略了特权变更的核心点。
2025-10-28 上游创建了一个拉取请求,基于 AI 生成的代码,为 D-Bus 服务添加了一个身份验证层。
2025-10-28 上游很快关闭了未合并的拉取请求。讨论听起来上游不再打算在此仓库中支持 scx_loader D-Bus 服务。
2025-11-03 我们对问题讨论提供了更详细的回复
2025-11-04 上游关闭了 GitHub 问题,并将 scx_loader 分离到一个专用仓库中。
2025-11-06 我们在新仓库中克隆了原始问题
2025-11-06 本报告发布。

10) 链接