从 PwnEscape 看 Agent 沙箱:microVM 不是绝对边界
PwnEscape 利用 virtio-net/TAP 的 L2/L3 偏移错配,从 Guest 打到 KVM 宿主,编号 CVE-2026-99999。它提醒所有跑 Agent 沙箱的人:把不可信代码关进 microVM,前提是宿主内核没有能被 Guest 打穿的路径。

图:PwnEscape 研究博客首页,副标题写明 Guest-to-Host KVM Escape(来源:pwn.ai/blog/pwnescape,截图于 2026-10-19)
PwnEscape 这个名字起得很直白:Pwn + Escape。PWNAI 的研究者 Paulos Yibelo 用它拿下了一条从 KVM Guest 到 Host 的完整逃逸链,编号 CVE-2026-99999,Debian 安全跟踪页给的解释是 Linux 内核 IPv4 校验和校验缺失。
这条链之所以值得单独写,不是因为它技术多花哨,而是因为它打在了当下最热的基础设施上:Agent 沙箱。
Agent 沙箱长什么样

图:Firecracker microVM 官方文档,Agent 沙箱最常见的隔离方案之一(来源:firecracker-microvm.github.io,截图于 2026-10-19)
现在主流的 Agent 执行环境大致三类:
| 方案 | 隔离粒度 | 典型实现 | 逃逸代价 |
|---|---|---|---|
| 容器 | 命名空间 + cgroups | Docker、containerd | 内核漏洞即可逃逸 |
| 用户态内核 | syscall 拦截 | gVisor | 需打穿用户态内核 |
| microVM | 硬件虚拟化 | Firecracker、KVM | 需 Guest→Host 漏洞 |
Agent 场景偏爱 microVM,因为启动快(百毫秒级)、隔离强(独立内核)、可以按任务销毁重建。Firecracker 就是为这个设计的:每个函数调用跑在一个小 VM 里,跑完就扔。
PwnEscape 打的正是这条边界。它不需要你配错权限、不需要你挂载 docker.sock,只需要 Guest 能发网络包——而任何能联网的沙箱都能发。
漏洞的物理过程

图:Linux 内核 virtio 驱动文档,PwnEscape 的入口就在 virtio-net 的收包路径(来源:kernel.org,截图于 2026-10-19)
前面那篇 CVE-2026-99999 的文章里我写了内核侧的细节,这里从攻击者视角再过一遍。
关键函数是 __virtio_net_hdr_to_skb()。Guest 发一个包,virtio-net 描述符里带着校验和元数据,宿主内核要把这些元数据翻译成 skb 字段。其中 csum_start 是一个偏移量。
问题在于这个偏移量是相对于当前 skb 数据视图的,而内核后来要拿它去算相对于网络头的位置。当报文带有额外的链路层结构时,两个坐标系差了一截。历史代码只检查了“在当前视图里别太靠前”,没检查“离网络头够不够远”。
攻击者构造一个带畸形校验和元数据的报文,让 csum_start 落在一个“通过旧检查、但实际越界”的位置。等内核调用 skb_checksum_help 完成校验和时,写入位置就落到了网络头之外——这就是内存破坏的起点。
从“能写坏网络头”到“能控制宿主执行流”,中间还有一堆工作(信息泄露、堆布局、绕过缓解),但 PwnEscape 的完整报告证明这条路走得通。对防守方来说,入口已经成立就够了。
为什么 Agent 沙箱特别危险
传统云厂商的 KVM 逃逸,影响的是“一台虚拟机逃到物理机”,利用门槛高、目标少。Agent 沙箱不一样:
- 沙箱里跑的是模型生成的代码,来源不可信,而且量极大;
- 每个任务一个 VM,意味着攻击者有无限次尝试机会;
- 沙箱通常允许出网(要调 API、装依赖),攻击面天然打开;
- 多租户共享宿主,一个逃逸影响所有租户。
换句话说,Agent 沙箱把“不可信代码”变成了常态,而不可信代码正是内核漏洞最好的触发器。
防守方的清单
我按优先级列几条,都是能落地的:
1. 先修内核,再谈架构
CVE-2026-99999 的修复已经在 7.3-rc7 合入。Debian bookworm 修在 6.1.190-1(DLA-4841-1),trixie 修在 6.12.113-1(DSA-6568-1)。跑 KVM 的宿主机,升级 + 重启是第一步,没有替代方案。
sudo apt update && sudo apt full-upgrade
sudo reboot
uname -r # 确认运行的是修复后的内核
2. 别把 microVM 当唯一边界
分层防御的意思是:即使 VM 被逃逸,还有下一层。具体包括:
- 宿主上的沙箱进程用 seccomp 限制 syscall;
- 用 Landlock 限制文件访问;
- 网络出站走代理,按域名白名单放行;
- 沙箱进程以非 root 运行,且没有额外 capability。
这些措施不能阻止内核漏洞,但能限制逃逸后的横向移动。
3. 限制沙箱的网络能力
PwnEscape 的入口是网络包。如果沙箱只需要调特定 API,就别给它完整的出网能力:用 HTTP 代理 + 域名白名单,禁止任意 TCP;禁止 raw socket;禁止非必要协议。
4. 快速销毁与重建
沙箱的价值之一是“跑完就扔”。把生命周期压到最短,定期轮换宿主,能降低被长期驻留的风险。配合快照回滚,异常时直接丢弃整台 VM。
5. 监控异常
Guest 里出现异常的网络包模式、宿主上出现异常的提权尝试、沙箱进程访问了不该访问的路径——这些都应该触发告警。逃逸利用通常不是一次成功,会有大量失败尝试。
microVM 的启动流程与攻击面
Firecracker 这类 microVM 的启动大致是:宿主上的 VMM 进程创建 KVM 虚拟机 → Guest 内核启动 → 挂载最小 rootfs → 运行用户代码 → 退出销毁。整条链上,Guest 能碰到宿主的地方只有几个:
- virtio 设备:网络、块设备、串口,Guest 通过共享内存和 MMIO 与宿主通信;
- KVM ioctl:Guest 内核退出(VM exit)时,宿主 VMM 处理;
- 共享文件系统:如果配置了 virtiofs 或 9p。
PwnEscape 打的是第一个。virtio-net 的收包路径在宿主内核里,Guest 构造的元数据要经过 __virtio_net_hdr_to_skb 翻译,翻译错了就是内存破坏。这条路径之所以肥,是因为它同时处理“Guest 可控数据”和“宿主内核结构”,任何一个偏移算错都可能被利用。
seccomp 与 Landlock 怎么配
逃逸成功之后,攻击者还要在宿主上做事。这一层能拦多少,取决于沙箱进程的权限。我一般这样配:
# 最小 syscall 集合,按实际需要增减
# 用 seccomp-tools 或 oci-seccomp-bpf-hook 生成基线
原则是三条:
- 沙箱进程以非 root 运行,
no_new_privs=1; - 只保留运行所需 syscall,
execve、ptrace、mount这类高危调用默认拒绝; - 用 Landlock 限制文件访问到任务目录,其他路径一律不可读。
这些措施不能阻止内核漏洞,但能把“逃逸后拿到宿主 shell”变成“逃逸后困在一个受限进程里”。
出网白名单
PwnEscape 的入口是网络包。如果沙箱只需要调特定 API,最有效的缓解是不让它自由出网:
- 走 HTTP 代理,代理只放行白名单域名;
- 禁止 raw socket 和非常规端口;
- 记录所有出站请求,异常目标直接阻断。
这条措施的成本比打内核补丁低,收益却很高:即使漏洞存在,攻击者也要先找到一条能发任意包的路。
怎么红队测自己的沙箱
我会做三类测试:
- 越权文件访问:在沙箱里尝试读
/etc/shadow、宿主挂载点,看是否被 Landlock 拦住; - 网络探测:尝试连接内网地址、非白名单端口,看代理是否拒绝;
- 资源耗尽:fork 炸弹、内存分配、磁盘写满,看 cgroup 限制是否生效。
三类都不涉及内核漏洞,但能验证“逃逸之后的第二层”是否真的存在。很多沙箱的隔离只做了第一层,第二层是空的。
常见误区
误区一:“用了 microVM 就绝对隔离”。microVM 隔离的是 Guest 和 Host,边界是宿主内核。宿主内核有漏洞,隔离就被打穿。Firecracker 的攻击面比完整 QEMU 小,但不是零。
误区二:“补丁打了就没事了”。补丁修的是已知漏洞,攻击者还会找下一个。沙箱的价值在于“即使被打穿,损失可控”,而不是“永远不会被打穿”。
误区三:“沙箱里跑的是模型代码,没人会攻击”。模型代码来自不可信输入,攻击者可以通过提示注入让模型生成恶意代码。沙箱里的代码,默认就是敌意代码。
我的判断
PwnEscape 最值得记住的不是技术细节,而是一个事实:隔离边界是内核,而内核会有漏洞。你把不可信代码关进 microVM,等于把信任押在“宿主内核没有 Guest 可利用的 bug”上。这个假设在工程上合理,但不能当成绝对安全。
对做 Agent 产品的人,我的建议是:沙箱要按“会被打穿”来设计。假设逃逸会发生,然后问自己——逃逸之后攻击者能碰到什么?如果答案是“整个宿主和所有租户数据”,那架构就有问题;如果答案是“一个受限的、随时可销毁的临时环境”,那才叫纵深防御。
CVE-2026-99999 会被修掉,下一个内核漏洞还会来。边界会一直存在,问题是你在边界后面放了几层。

评论
…