BONAN RUAN / 工业技术研究

容器运行时边界研究

Dilemma

只读保护之后,边界仍在内核

这是我在工业界开展的技术研究:从 runC 的只读保护与共享内核之间的信任边界出发,研究受限条件下的 ELF 文件注入,以及如何保留程序原有行为。

KCon 2022 · 原演讲题名 · 考虑到攻击危害,源码未公开

只读保护与共享文件对象之间的边界 容器中的运行时引用经过只读挂载,仍关联宿主机的 runC 文件。普通写入在只读边界停止,历史漏洞 Dirty Pipe 则从共享内核改变同一文件的页缓存。 CONTAINER / 容器HOST / 宿主机 /proc/…/exe运行时可执行文件引用 runC / ELF ro-bind 普通写入在边界被阻止 仍是宿主机的文件对象 SHARED KERNEL / 共享内核 Dirty Pipe · 2022 年的漏洞前提 经内核页缓存改变内容 ACCESS POLICY ≠ FILE IDENTITY 容器、只读保护与共享内核 普通写入被只读保护阻止,但在本文的历史漏洞条件下,Dirty Pipe 可经内核改变宿主机 runC 文件对象。 CONTAINER / 容器/proc/…/exe只读访问 HOST / 宿主机runC / ELF 只读挂载 · 仍关联同一文件 共享内核 / PAGE CACHEDirty Pipe · 历史漏洞条件
只读权限 ≠ 独立副本2022 / 机制示意
工业技术研究

阮博男 · 绿盟科技星云实验室

KCon 2022

北京 · 技术分享

runC + Linux

运行时与内核的信任边界

源码未公开

依据公开文章与演讲展示

边界改变了,文件未必改变

研究从两个问题展开:只读保护依赖什么?文件内容被改变以后,原有运行时行为是否还会继续?

01 / 对象身份

命名空间隔离,不等于文件副本

runC 进入容器命名空间执行初始化。符合权限条件时,/proc 的可执行文件 magic link 可关联命名空间外的文件对象;路径不可见与对象不可达并不是一回事。

02 / 保护前提

只读挂载,仍然信任内核

2019 年修补采用只读 bind mount,阻止普通写入并降低复制成本。2022 年 Dirty Pipe 改变的是内核页缓存,绕过这一用户空间保护前提;它不要求旧 runC 漏洞仍未修复。

03 / 工程约束

改变控制流,保留原有逻辑

覆盖整个二进制会破坏运行时。研究改用静态 ELF 文件注入:受文件大小、可用空间与 PIE / ASLR 约束,新增逻辑之后仍回到原入口继续执行。

来源:技术文章 §2、§4;slides 第 6–14、19–25 页。以下展示对应 2022 年公开研究中的历史机制,不据此判断当前 runC 版本。 WP ↗

换一种保护,看边界在哪里

选择运行时保护与写入条件,逐步观察:引用了什么对象,哪一层阻止写入,宿主机原文件是否仍在路径上。

DILEMMA / 文件对象与写入边界机制模型 · 非现场测试

CONTAINER / 容器侧

RUNTIME REFERENCE/proc/…/exe

只读挂载上的可执行文件

HOST / 宿主机侧

ORIGINAL FILErunC / ELF

只读挂载与宿主机 runC 关联同一文件对象

同一文件对象

LINUX / PAGE CACHEDirty Pipe 历史漏洞条件
对象关系
宿主机原文件
写入条件
等待观察
宿主机文件
等待观察

选择保护与条件,然后逐步观察文件引用如何穿过容器边界。

保护在何处生效

先分清权限与对象

只读限制改变访问策略,未必创建独立文件副本。推进流程,比较它与 memfd 的边界。

起点:只读挂载 + Dirty Pipe 历史条件WP §2.3 · 修补方案 ↗

来源:WP §2.3、slides 第 12–14 页。memfd 方案先复制文件并封印;只读方案保留同一对象。2019 年的成本与兼容性讨论解释了方案选择。封印副本在本文范围内隔离宿主机原文件,不代表抵御任意内核漏洞。

让原有程序,继续往下走

研究的改进不是替换整个 runC,而是在受限空间内重定向入口,再恢复原控制流。运行正常,不等于文件可信。

runC / ELF文件大小保持不变 · 非比例示意
原入口新增逻辑(抽象)原 runC 流程
父进程 → 原入口子进程 → 独立逻辑

原入口直接进入 runC 的原有逻辑。

静态文件注入 / 研究示意

原始文件,原始入口

这里展示文件结构与控制流关系。研究讨论的是运行前修改 ELF 文件,而非向运行中进程注入内存。

两个必须一起解决的约束

Dirty Pipe 不能增大文件,也不能从页面边界开始覆盖。PIE / ASLR 让实际加载地址变化,因此回到原入口需要考虑运行时地址关系。图中不包含载荷、偏移或利用实现。

157B

文中实现的载荷长度

用于说明当时的空间约束;本页不提供载荷内容。

168B

动态构建样本的 NOTE 空间

文中测试构建可容纳该长度;来自特定样本的节布局。

68B

静态构建样本的 NOTE 空间

不足以沿用同一位置。研究另探可复用区域,并验证该样本后续运行。

来源:WP §4.2–4.3;slides 第 20–25 页。字节数是当时特定构建的布局与实现值,不是成功率、性能成绩或跨版本保证。文件布局的兼容性仍需逐构建核验。 Slides ↗ · Dirty Pipe 原始分析 ↗

同一机制,两个进入时机

业务容器与镜像供应链的进入时机不同。切换场景,查看研究前提、演示结果与可观察的防御位置。

运行时再次进入已有容器

已具备容器内执行能力。在运行时再次进入该容器的历史场景中,进程与文件引用的关系成为研究观察点。

前提
需要满足进程可见性、权限与漏洞条件;不以动态链接作为此场景的一般前提。
原始演示记录
文中 exec 演示仍出现错误信息,但后续 Docker 创建新容器正常。保留原逻辑不意味着完全无异常。

Slides 第 16、26 页 · WP §3.1、实验一 ↗

DEVELOPMENT

减少业务入口

业务安全与最小权限决定进入容器的难度。文章建议尽量采用 rootless,减少后续利用能够依赖的权限。

DEPENDENCIES

检查镜像与供应链

文章建议镜像扫描与白名单。镜像路线的运行时构建与加载条件必须核验,不能将所有镜像部署都视为相同场景。

RUNTIME

检查文件,而不只看功能

更新内核与运行时,监控宿主机 runC 修改和异常进程行为。容器还能启动,并不足以证明运行时文件未被改变。

场景与演示:WP §3.1、§4「效果测试」,slides 第 16、26–27 页。防御观点:WP §5,slides 第 31–32 页;此处忠实呈现当时研究建议。

Dilemma: runC’s Achilles’ Heel

KCon 2022 工业技术研究演讲。文章《进退维谷:runC 的阿克琉斯之踵》记录保护方案、ELF 文件注入约束与两种场景的演示。研究没有独立项目名称,页面沿用原演讲题名。

Bonan Ruan / 阮博男 · 绿盟科技 · 星云实验室 · 当时任安全研究员

考虑到攻击危害,我没有公开这项研究的源码。这里通过原创图形解释公开资料中的机制,并保留原始研究的前提与适用范围。

展开机制来源与阅读路线