目录

1) 引言

Stalld 是一个旨在防止 Linux 操作系统线程饿死的守护进程。它最近被添加到 openSUSE Tumbleweed 中,我们对其包含的 systemd 服务进行了例行审查。在审查过程中,我们发现了一些应予解决的安全问题。

我们通过其 GitLab 问题跟踪器联系了上游,并创建了一个公开问题和一个私有问题(仍为私有),但从未收到任何回复。在近三个月没有得到答复后,我们决定现在发布现有信息。

本报告基于 stalld 版本 v1.19.6。

2) 在 scripts/throttlectl.sh 中使用固定临时文件路径 /tmp/rtthrottle

stalld 的 systemd 单元中作为预脚本和后脚本调用的 throttlectl.sh 脚本,使用固定的 /tmp 路径 /tmp/rtthrottle 来缓存从 /proc/sys/kernel/sched_rt_runtime_us/proc/sys/kernel/sched_rt_period_us 中找到的原始值。这允许进行符号链接攻击和文件预创建攻击。

只有当 Linux 内核的 protected_symlinks 设置未生效时,符号链接攻击才能成功。如果那样的话,攻击者就可以在指定位置放置一个符号链接,导致 throttlectl 覆盖系统中的任意文件,从而可能导致本地拒绝服务。

2.b) 文件预创建攻击

/tmp/rtthrottle 中预创建文件将始终有效,即使内核中的 protected_regular 设置已激活。这是因为脚本中的 shell 重定向(如 echo $period > $path/sched_rt_period_us 这一行)在 open() 标志中,如果创建文件失败,将回退到不带 O_CREAT 打开目标文件。没有 O_CREATprotected_regular 逻辑将不再触发。

这意味着,如果本地攻击者预先创建了该文件,脚本将写入攻击者拥有的文件。当脚本尝试从该文件中恢复值时,本地攻击者可以在其中放置任意值,这些值随后将被写入 /proc/sys/kernel/sched_rt_* 中的伪文件中。这是一种本地拒绝服务或本地完整性破坏。它不是信息泄露,因为这些伪文件的内容对所有人都是可访问的。

2.c) 可利用性

当 stalld 在启动时启动时,未经授权的本地用户对此问题几乎没有利用机会。然而,如果服务在稍后启动或重新启动,则该攻击向量是可利用的。

2.d) 建议的修复方法

为了解决这个问题,我们建议将文件放置在由 root 拥有的 /run/stalld 目录中。该目录已通过 stalld 的 systemd 单元创建。

在 systemd 单元中,还可以应用 PrivateTmp=yes 等加固措施,以防止将来发生此类临时文件问题。

throttlectl 脚本还应设置 errexit shell 选项,使其在任何意外错误时退出。

3) fill_process_comm() 函数可能读取意外的控制字符

从系统中可能不受信任的进程读取 /proc/<pid>/comm 的内容的 fill_process_comm() 函数。那里找到的数据来自内核执行的可执行文件的名称。可执行文件名称可以包含除 / 字符以外的任何数据。这还包括控制字符,如 \r 甚至终端控制序列。stalld 使用此字符串将信息写入日志。通过在可执行文件名中嵌入回车符,本地攻击者可以实现日志欺骗。

为了解决这个问题,我们建议将字符串中的所有非字母数字字符转换为一个安全字符,如 ?

4) 实验性的 FIFO 提升功能可能存在锁定系统的风险

通过 --force_fifo 命令行开关,可以指示 stalld 通过将任务切换到 SCHED_FIFO 调度来“提升”被卡住的任务。我们想知道如果一个“恶意任务”被分配到这个调度程序会发生什么。据我们所知,如果这样一个任务不再释放 CPU,整个系统可能会被锁定。这可能需要 stalld 本身在 SCHED_FIFO 下运行,使用比被提升任务更高的调度优先级,以防止这种情况发生。

5) 访问 /proc/<pid>/{status,comm} 时可能出现的竞争条件

像往常一样,在迭代 /proc 文件系统中的进程时,可能会发生竞争条件。目标进程可能试图通过其他进程替换自身,从而使 stalld 混乱。但我们不认为“卡顿”情况可以轻易被本地攻击者所诱发,因此从这个方向利用任何可能性的几率可能很小。

我们只是在此作为提示,也许我们忽略了更关键的东西。

6) daemonize() 中使用的奇怪 umask() 设置

通过调用 umask(DAEMON_UMASK)daemonize() 函数向守护进程应用新的 umask。但其常量具有奇怪的值。

/*
 * Daemon umask value.
 */
#define DAEMON_UMASK  0x133  /* 0644 */

我们不知道为什么不首先使用八进制 0644 值,而是将其作为注释。常量 0x133 对应于八进制值 0463。它将屏蔽所有者可读位、组的读写位以及世界的读写执行位。这可能不是这里的意图。

幸运的是,不会因此产生任何世界可写文件,但这种错误配置可能会导致未来出现奇怪的效果,例如,因为文件的所有者将对其没有读取权限。

我们不认为这是一个安全问题,因此我们在此创建了一个上游 GitLab 跟踪器中的公开问题

7) CVE 分配

由于上游没有做出反应,因此也未确认任何这些问题,所以我们至今尚未从 Mitre 请求任何 CVE。固定的临时文件使用问题 2) 可能值得分配 CVE。

8) 时间线

2024-09-09 我们已在上游 GitLab 项目中报告了这些问题(12),并为敏感问题提供了协调披露。
2024-11-13 在长时间未收到回复后,我们在问题中评论,要求在 2024 年 11 月 22 日前回复,否则我们将自行发布该问题。
2024-11-28 我们在上游修复程序可用之前发布了这些信息。

9) 参考资料