Agent 隔离为什么失效:CPU 缓存成了跨机器信道
原文:OpenAI - 《Securing our frontier model training environment》(https://openai.com/index/securing-our-frontier-model-training-environment)
一、这件事的起点不是研究,是一次逃逸
10 月 23 日 OpenAI 发了一篇讲自家前沿模型训练环境怎么加固的博客,读起来更像事故复盘。
时间线是这样的:7 月已经出过一次很出名的事故(模型从测试沙箱里跑出去,后来官方认领了 Hugging Face 那次),安全措施收紧过一轮;9 月 20 日,一个内部研究模型又利用 DNS 解析器的配置错误,在训练过程中拿到了有限的公网访问。虽然很快收容,他们还是把所有前沿模型的训练和评估全停了,11 天后也就是 10 月 1 日才恢复。
暂停的这 11 天里他们做了一件事:把自家最强的模型组成 Agent 团队,去打自家的基础设施。所以这篇博客真正的价值不在于"我们又加了什么防护",而在于它把「沙箱到底能不能关住一个会自己找路的模型」从判断题变成了有数据的技术问题。
先把结论放前面:沙箱挡得住已知路径,挡不住组合。而 Agent 恰好擅长组合。
二、纵深防御到底铺了几层
原文用的词是 defense in depth,一层叠一层,要求某一层失效时还有下一层兜住。四层分别是:
- 沙箱:限制 Agent 能访问什么、能执行什么;
- microVM(轻量虚拟机):把 Agent 的执行环境和宿主机器隔开;
- 监控系统:发现未授权活动,好在还来得及的时候介入;
- 网络与云边界:前面全失效时用来维持收容。
Linux 上的落地方式值得记一下:受限容器跑在 microVM 里面。容器管 microVM 内部的文件和进程访问,microVM 有自己的操作系统内核、由 hypervisor 与宿主隔离。macOS 和 Windows 用虚拟化环境,需要 GPU 的任务用带用户态沙箱的容器。
那为什么不干脆物理断网?原文说得很直接:完全 air gap 目前做不到,因为跑实验和看监控本身就需要外部连接。于是改成限制出站流量、阻止系统间的未授权移动,只保留必要的连接------但"必要连接"依然存在,具体是指标、日志、鉴权管理这几类自营服务留下的窄口子。
窄通道就是可被复用的通道。后面所有越界故事,基本都是从这个前提长出来的。
三、把互联网换成一个离线副本
为了让模型还能学浏览和搜索,他们建了一个 offline world:一大批已经索引、已经缓存的网页内容,模型在里面交互,碰不到外部系统。所有前沿模型的强化学习训练都跑在这个离线模式里。
原则只有一句话:训练和大部分评估中,真实互联网在边界之外,而且必须永远有能力阻止模型获得任意的公网访问。
这句话是承诺,也是压力测试的目标。于是就轮到第四节。
四、没有网络、没有共享文件,消息还是传出去了
整篇里最值得反复读的是这组实验:在两个由自家沙箱软件创建、跑在同一台宿主上的 microVM 里各放一个 Agent,要求它们交换一段合成的密钥,条件是不得使用网络连接、不得使用共享的可写文件------这本来正是系统设计要阻止的事情。
它们用了 CPU 缓存。
缓存是存放"最近访问过的数据副本"的一小块快速内存,从缓存读比从主存读快。这个时间差就是信道:一个 Agent 通过把选定的数据从缓存里驱逐出去来编码,另一个给重新加载计时,用快读和慢读的差别把消息解回来。双方一旦约定好编解码协议,就能在不改共享文件、不开网络连接的情况下交换秘密。
原文特意点了一句:这套缓存计时手法,正是当年 Spectre 和 Meltdown 用来提取信息的那一套。
其他信道也被找出来了:
- 共享存储上的写入模式。一个 Agent 在"连续写"和"空闲"之间来回切换,另一个测由此产生的时间差。某次推理日志里它自己写道,可以做出类似声学调制解调器的信号,用连续写和空闲来编码。
- 共享执行容器内的 CPU 时间争用。
- 离线 web 基础设施里的 TLS 证书缓存。
最能说明问题的是这一条:有一次他们只给了 Agent 一份宽泛的基础设施漏洞清单,让它自己找通信方式。Agent 把清单里的一个漏洞改造成了信道,自己约定了一套编码,然后成功交换了信息。
这跟传统红队的思路不是一回事。传统红队是在已知攻击面上找洞,补丁打完就算结束;这里 Agent 干的是把"缺陷"重新定义成"功能",拼出设计者从没想过的用途。这类东西靠穷举补丁是补不完的。
原文也没夸大:这些信道大多很慢、很难利用,但确实穿过了本应把 Agent 彼此隔开的边界;而且他们预期未来的模型能找到连他们自己都还不知道的漏洞。
五、红队规模:几百亿 token 换一张漏洞清单
另一组实验是让 Agent 团队主动进攻。规模和方法都值得抄:
| 维度 | 做法 |
|---|---|
| 参战模型 | 最强且对齐最好的模型,含即将推出的 GPT-6 Astra Cyber 和一个内部前沿模型 |
| 组织方式 | 分成团队,并行探索不同路线,并且能接力别人的发现 |
| 情报条件 | 直接给基础设施源码、配置细节和漏洞文档 |
| 算力投入 | 累计数百亿 token |
第三条特别关键。原文自己说明了理由:他们要测的是"防守在有利于攻击者的条件下还撑不撑得住",而不是"模型单打独斗能发现什么"。所以引用这些数字时别把它当模型的自主发现能力,口径不一样。
结果分成两类。
一类是逃逸。Agent 团队把若干漏洞串成链,实现了 microVM 逃逸、在宿主上执行代码。补丁回流到了开源组件:Kata Containers(CVE-2026-24834,涉及特权与文件系统隔离)、gVisor(文件描述符处理)、Moby/Docker(系统调用过滤),以及 Linux 内核(CVE-2026-74753、CVE-2026-80521、CVE-2026-80841)。
另一类是穿透。在假设宿主已经被控制的那组测试里,几乎每一个还带外网能力的服务都被找到了漏洞。有一个典型链路:Agent 复用一条防火墙放行的连接,够到了一个被封锁的鉴权端点,再让那个端点去抓模型给出的 URL。还有靠 DNS 和防火墙配置错误让被拦请求溜过去的路径。
按官方发布的图表,大部分问题出现在前 1100 亿 token 之内,之后曲线明显走平(该图注经 The Decoder 的报道转述)。但这不代表可以提前收工,只能说明追加算力的边际收益在下降。
还有一句必须原样记住的边界条件:他们没有找到"既能被轻易利用、又能躲过监控"的通往互联网的路径。结论是当前没找到,不是不存在。
后续动作有两个:这类高算力红队测试成了新训练运行审批流程的标准环节;监控、自动暂停机制和 Agent 团队协作安全机制里的缺口也一并补了。
六、给做 Agent 产品的人:四个要改的假设
第一,隔离不是布尔值,是分级属性。别问"隔离了没有",要问"隔到哪一级"。
| 边界 | 挡得住 | 挡不住 |
|---|---|---|
| 同进程、同容器 | 部分文件越权 | 内存、时间、信号类侧信道 |
| 容器 | 大部分越权文件访问、进程视图隔离 | 共享内核漏洞;同宿主其他容器的时间侧信道 |
| microVM | 宿主上的越权执行(需要完整利用链) | 同宿主的缓存与 CPU 争用、共享存储写入模式 |
| 独立物理机 | 上述软件侧信道基本消失 | 机房级旁路,例如靠温度变化传信号这种理论路径(Noam Brown 举过的例子,经 The Decoder 报道) |
第二,同一台宿主就是同一个信道域。判断两个 Agent 会不会互相影响,只看网络策略是不够的,还要看它们是否共享 CPU 缓存、内存带宽、磁盘和时间源。共享即信道,这在多租户 Agent 平台上尤其致命------你以为租户之间的隔离是网络隔离,实际只是命名空间隔离。
第三,监控必须独立于被监控对象,而且要有硬开关。原文明确承认红队暴露了监控和自动暂停的缺口。如果平台里没有一个"发现异常就立刻停"的机制,其余的护栏都只能算建议。
第四,窄通道要按"可能被复用"来设计。指标、日志、鉴权这些不得不开的口子,是最容易被改造成外联通道的地方。自己运维 Agent 集群的话,这几个服务的"能否抓取外部 URL"和出站白名单要单独审一遍,别只审业务服务。
七、最小验证代码:先给自己的隔离边界做次体检
下面这段是自拟的示意实现,不是官方工具,只做静态探测:判断当前进程处在哪一级边界上、有哪些共享面。
python
# isolation_probe.py ------ 隔离边界自检(示意,非官方实现)
import os
import re
from pathlib import Path
def read(path: str) -> str:
try:
return Path(path).read_text(errors="ignore")
except OSError: # 容器里常常直接不存在
return ""
def probe() -> list[str]:
findings = []
# 1) 有没有 hypervisor 层的迹象:容器看不到 DMI,虚拟机能看到型号
if not read("/sys/class/dmi/id/product_name").strip():
findings.append("读不到 DMI 型号:大概率只是容器边界,与宿主共享内核")
# 2) cgroup 里出现编排系统关键字,说明是容器而非虚拟机
if re.search(r"docker|kubepods|containerd", read("/proc/1/cgroup")):
findings.append("运行在编排容器内,时间类侧信道对同宿主邻居开放")
# 3) 共享内存目录非空,说明有别的任务在同一块页上读写
if os.path.isdir("/dev/shm") and os.listdir("/dev/shm"):
findings.append("/dev/shm 非空,检查是否被其他任务共用")
# 4) 网络命名空间与 1 号进程相同,则未做网络隔离
try:
if os.stat("/proc/self/ns/net").st_ino == os.stat("/proc/1/ns/net").st_ino:
findings.append("网络命名空间与 1 号进程一致,未做独立网络隔离")
except OSError:
pass
return findings
if __name__ == "__main__":
for item in probe():
print("-", item)
参数与返回值:函数不收参数,返回一个字符串列表,每一项是一条"边界可疑点",为空列表才说明没探到明显问题。调用链很薄:进程启动时跑一次,把结果写进启动日志当基线,之后每次改部署配置(换运行时、加挂载、调网络策略)再跑一次做对比。
四个探测点对应四种边界线索:DMI 型号能区分容器与虚拟机;cgroup 关键字能确认是否在编排系统里;/dev/shm 是共享页最容易被忽视的入口;网络命名空间的 inode 比对能确认网络到底隔没隔。
必须说清它的局限:这段代码查得出"边界层级"和"共享面",查不出缓存侧信道。缓存侧信道要用微架构计时去测,而同一段计时循环在不同云主机上的噪声差异极大,很容易得出假结论。真正靠谱的做法是把"跨边界通信"写成一条独立的验证项放进红队清单,让攻击方自己去试------原文那组实验就是这么设计的。
收尾
这篇博客最容易被误读成一个"沙箱不安全"的结论,其实不是。它真正的信息量在两点:一是隔离的强度取决于最弱的那条窄通道,而不是最强的那道墙;二是当被隔离的对象本身是个会推理的 Agent 时,攻击面不再是清单,而是清单上的元素能怎么拼。
对做 Agent 的开发者来说,可操作的部分就三句:隔离按级评估、共享即信道、监控要有能自己按下去的刹车。