flawopen.com/Teardowns/cve-2024-21626-runc-container-breakout-leaky-vessels
CVE-2024-21626:runc 如何修复 Leaky Vessels 容器逃逸
CVE-2024-21626 将 runc 内部文件描述符泄漏与 chdir 后缺少边界检查结合起来。根据启动路径,容器可能以位于主机命名空间的工作目录启动,或通过相关 exec 路径访问主机文件。[1], [2], [3]
💡 通俗易懂的原理解析 (ELI5)
runc 为容器准备隔离空间。内部句柄并未直接交给容器进程,但漏洞可能让所选工作目录解析到空间边界之外;修复会检查最终目录,并在启动容器程序前关闭内部句柄。这是运行时目录边界检查问题,并非普通的容器文件继承。[1], [2]
核心概念与专有名词
框架映射
- 本指南所述弱点的 CWE 参考: CWE-403
根本原因剖析 (Root Cause)
在 runc 1.1.12 之前,内部文件描述符可能泄漏到 runc init。它们属于 runc init 内部,并非直接继承到容器进程。在攻击 1 和 2 中,配置或指定的工作目录可能通过主机挂载命名空间的句柄解析;runc 在 chdir 后没有验证最终 cwd 是否仍位于容器根目录内。其他变体利用 execve 路径处理覆盖主机二进制文件。1.1.12 版本加入四层措施:利用 Linux Getwd/ENOENT 行为验证 cwd,在 execve 前立即关闭内部描述符,修复已知泄漏,并在启动 runc init 前将非 stdio 描述符设为 close-on-exec。[1], [2], [3], [4]
CVE-2024-21626:攻击变体与 runc 1.1.12 的对应防护
| 公告中的变体 | 必要条件与边界 | 对应修复或临时措施 |
|---|---|---|
攻击 1:通过 runc run 启动恶意镜像 | 需要诱使特权用户启动不可信镜像。镜像配置的工作目录可能解析到容器挂载命名空间之外。 | chdir 后的 unix.Getwd() 检查会在 cwd 位于命名空间根目录之外时失败。临时措施是对不可信镜像只允许 cwd 为 /。 |
攻击 2:容器内进程配合 runc exec | 容器内进程需要预判管理员通过 runc exec --cwd 选择的非 / 工作目录,并在执行前准备相关路径。 | 相同的 chdir 后 cwd 检查会阻止容器外执行。修复前将 --cwd 限制为 /。 |
| 攻击 3a:启动镜像时覆盖主机二进制文件 | 这是攻击 1 的变体,仍要求特权用户启动恶意镜像;该变体跨越 execve 边界并触及主机二进制文件。 | 在 execve 前立即关闭 runc 内部描述符,修复已知泄漏,并设置 O_CLOEXEC。升级到 runc 1.1.12 或供应商已修复版本。 |
攻击 3b:通过 runc exec 覆盖主机二进制文件 | 这是攻击 2 的变体,起点是攻击者能够影响的运行中容器里的恶意进程;但仍需要有特权的管理员对该容器执行一次常规 runc exec 操作。公告指出,这条路径所需的用户交互少于镜像路径。 | exec 前关闭描述符和 close-on-exec 防护对应这一独立路径。维护者没有给出实用的临时绕过措施;请升级。 |
攻击执行流程分解
步骤 4
影响与条件
公告记录了四种变体导致的主机文件系统访问和主机控制。镜像攻击需要特权用户运行不可信镜像。攻击 3b 仍要求管理员对攻击者能够影响的容器执行一次常规 runc exec 操作。[1]
源代码对比:漏洞与安全实现
存在漏洞的实现
// 修复前行为的简化表示;并非 runc 原始源码
if err := os.Chdir(config.Cwd); err != nil {
return err
}
// 缺少检查:最终 cwd 是否位于命名空间根目录内
加固后的安全修复
// runc 1.1.12 上游边界检查(节选)
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
return errors.New("cwd 位于容器挂载命名空间根目录之外")
} else if err != nil {
return fmt.Errorf("无法验证 cwd 安全性:%w", err)
} else if !filepath.IsAbs(wd) {
return fmt.Errorf("cwd 不是绝对路径:%q", wd)
}
工程与系统安全加固清单
- ✓将 runc 升级到 1.1.12 或供应商支持的更高版本,并核实每个节点镜像中的实际运行时版本。[1], [4]
- ✓将不可信容器镜像视为代码执行请求;限制通过 Docker、Kubernetes 和构建流水线启动镜像的权限。[1]
- ✓运行时实现应在 chdir 后使用平台支持的命名空间根目录检查验证 cwd,出错时安全失败。[1], [2]
- ✓为内部描述符设置 O_CLOEXEC,并在 exec 前最后一个安全时点关闭非 stdio 描述符,同时考虑运行时自身需要的描述符。[1], [3]
- ✓不要把用户命名空间当作 runc 补丁的替代品;在支持时将其作为额外隔离层。[1]