目录

1) 引言

nvidia-modprobe 是专有的 Nvidia GPU 显示驱动程序的 setuid-root 辅助工具,用于加载内核模块并创建用户空间 GPU 访问所需的字符设备。通常,驱动程序通过 udev 来完成此操作。然而,内核许可限制禁止 Nvidia 的专有内核模块生成 uevent,而 uevent 是 udev 工作所必需的。因此,需要此特殊辅助工具。

我们在 openSUSE 中审查了 nvidia-modprobe,作为我们白名单流程的一部分,该流程要求对所有新引入的 setuid 二进制文件进行审计。我们审查的版本是 550.127.05,本报告基于该版本。上游在版本 550.144.03 中发布了 bug 修复和一个 安全公告

2) wait3() 作为 Setuid 程序中的侧信道

waitpid() 类似,wait3() 系统调用允许调用进程获取子进程的状态信息。与 waitpid() 不同的是,wait3() 还返回资源使用信息。此调用返回的测量值包括 CPU 时间、内存消耗以及较低级别的详细信息,例如子进程运行期间发生的次要和主要页面错误的数量。另请参阅 man 2 getrusage

令人惊讶的是,wait3() 也适用于 setuid 子进程,泄露了大量关于以提升权限运行的目标程序行为的信息。

尝试此操作的一种便捷方法是使用 GNU Time,这是一个小型实用程序,它会启动目标进程并打印 wait3() 的输出,例如

/usr/bin/time -v nvidia-modprobe

3) 文件存在性测试 (CVE-2024-0149)

在 nvidia-modprobe 的情况下,我们可以利用 wait3() 进行文件存在性测试。

使用选项 -f NVIDIA-CAPABILITY-DEVICE-FILE(任意路径)执行时,nvidia-modprobe 会执行以下步骤:

  • 尝试以 root 身份打开提供的路径
    • 如果路径确实存在
      • 读取一行或多行
      • 解析每一行(安全实现)
      • 静默退出,返回码 0
    • 如果路径不存在
      • 静默退出,返回码 0

事实证明,读取所提供路径的第一行有时会触发次要页面错误。页面错误的数量在多次执行中并非完全恒定,这取决于内核映射的页面是否脏。但是,如果文件不存在,则无法读取,因此不会触发任何页面错误。我们可以反复执行 nvidia-modprobe,计算页面错误的众数,并推断提供的路径是否存在,即使调用者没有必要的文件系统权限。

简化示例

$ /usr/bin/time -q --format=%R nvidia-modprobe -f /root/.bash_history
80

$ /usr/bin/time -q --format=%R nvidia-modprobe -f /root/does/not/exist
79

输出会有所波动,但只需重复几次即可从众数获得可靠的信号。

4) Bug修复

上游发布了一个 bug 修复。此提交在尝试读取之前将查询路径限制在 /proc/driver/nvidia 下的文件,从而消除了信息泄露。

5) CVE 分配

上游为此问题分配了 CVE-2024-0149。

6) 其他软件包

考虑到这种侧信道攻击相对晦涩的性质,我们决定简要查看其他几个表现出类似使用模式的软件包。

  • shadow
    • chsh:否
  • util-linux
    • mount -T:否
    • umount:否
  • v4l-linux:是,但不需要 wait3(),并且该问题已为人所知 (CVE-2020-1369)。

尽管我们没有找到此问题的其他实例,而且此漏洞的严重性相对较低,但它仍然是编写或审计 setuid 程序时需要牢记的众多陷阱之一。

7) 时间线

2024-10-02 我们注意到了这个问题,并在 bsc#1231257 中开始私下跟踪。
2024-10-09 我们通过电子邮件与 NVIDIA PSIRT 共享了信息,提供了协调披露。
2024-10-12 我们收到了来自 Nvidia 的初步确认。
2024-10-22 经过富有成效的讨论,主要是关于一些旁敲侧击的问题,我们同意 2025 年 1 月 16 日作为协调发布日期。
2025-01-16 Nvidia 分配了 CVE-2024-0149。
2025-01-16 Nvidia 发布了修复程序,作为版本 550.144.03 的一部分。

8) 参考资料