当新闻标题说某个AI智能体"逃出了沙箱",它到底翻过了哪堵墙?

打开网易新闻 查看精彩图片

有些沙箱是墙,有些只是帘子。容器、seccomp、gVisor、Firecracker、Kata、WebAssembly、V8隔离,它们可以排成一道阶梯,衡量标准只有一个:代码能够触达多少宿主机内核。而有一扇门,它们谁都没关上。

先问一句:为什么要沙箱?你的智能体会写代码,然后把它跑起来。可这代码到底是谁写的?把智能体产出的代码当成陌生人给的代码来对待,因为它可能真的是。

内核是整栋楼的水管

每个程序住在自己的公寓里,地下室是内核,也就是所有公寓共用的管道。打开文件、发送数据包,都得向内核请求,这个请求叫系统调用。Linux有几百个系统调用,每一个都是通向地下室的一根管子。管道上出一个漏洞,一个租户就能淹掉整栋楼。

容器是什么?同一栋楼里的一间上锁房间。命名空间控制你能看见什么:自己的进程、文件和网络;cgroups控制你能用多少:CPU、内存、进程数量。在Mac或Windows上,Docker跑的是一个Linux虚拟机,所以共享的内核是那台虚拟机的。

再往下一层是seccomp,一份"允许拧哪些水龙头"的清单。它是一个极小的过滤程序,检查每个系统调用的编号和参数,给出允许、报错或终止三种回答:read允许,write允许,mount拦截,ptrace拦截,kexec_load拦截,openat允许。但内核自己的文档写得很直白:系统调用过滤不是沙箱。

那Docker容器算沙箱吗?单靠自己不算。

2024年1月,启动容器的工具runc出现漏洞,容器可以触达宿主机的文件,编号CVE-2024-21626,这是一次完整的容器逃逸。而且出问题的甚至不是内核。

经典的自摆乌龙是把Docker套接字挂进容器:docker run -v /var/run/docker.sock:/var/run/docker.sock agent。谁控制了这个套接字,谁就控制了Docker守护进程,而守护进程以root身份运行。只挂卷、用独立网络会好一些,但共享的仍是同一个内核。所以那些说"如果它逃逸了,那它本来就不是沙箱"的人是对的:共享内核,就是帘子,不是墙。

往上爬:门童、小屋与手套箱

怎么让代码远离管道?雇一个门童。gVisor的Sentry就是一个用Go写的、跑在用户空间的内核。你的代码发起系统调用,由Sentry自己应答,它只向宿主机发出少量经过过滤的调用,文件操作则经由Gofer这个助手完成。代价是每次系统调用都要绕路,系统调用密集的工作会变慢;有些程序期待Sentry并不提供的管道,兼容性也会下降。

还嫌不放心,就搬出去住自己的小屋。微虚拟机让你的代码跑在独立的客户内核上,有自己的管道。Firecracker是虚拟机监视器,建立在Linux内部的KVM之上。小屋仍有一条计量收费的线路通向地下室,那就是KVM;要逃逸,代码得先骗过自己的内核,再攻破虚拟机监视器或KVM。不过,如果你共享了一个文件夹进去,就等于在那堵墙上开了扇门。

虚拟机不是很慢吗?Firecracker的规格写着,客户机在125毫秒内到达init:0.000秒实例启动,0.003秒客户内核引导,0.125秒执行/sbin/init。虚拟机监视器自身最多只增加约5MB内存。前提是机器有/dev/kvm。同时跑很多沙箱也没问题,它们之间只有在你接上网络时才会互相通信。这也是E2B这类平台把沙箱描述为按需创建的Linux虚拟机的原因,连Docker自家的智能体产品Docker Sandboxes,也是让每个智能体跑在自己的微虚拟机里。

可你的工具说的是容器语言,不是虚拟机。Kata Containers就在容器接口后面放了一台轻量虚拟机:前门和公寓一样,门后却是一栋小屋,你的容器工具根本不知道自己在跑虚拟机。

再换一种思路:大堂里放一个密封的手套箱,你只能通过递进来的手套操作,别的什么都碰不到。WebAssembly模块的内存有边界检查,越界就触发陷阱,根本没有通向地下室的管道。JavaScript同理,V8是Chrome的JavaScript引擎,一个隔离实例就是它的一个密封副本,拥有自己的内存和仅被授予的API。Cloudflare Workers在一个进程里跑成千上万个隔离实例,Cloudflare称它们的启动速度约是容器化Node进程的100倍,内存占用只有十分之一。

那手套箱就是最强的一级?别急。现在墙变成了运行时本身,而运行时有漏洞。攻破一个,你就是宿主机上的普通进程。2024年,V8团队表示,即便其最新的沙箱也还不算强边界。侧信道依然有效,通过给共享CPU计时,连小屋之间都能窥探。

所有墙都没关上的那扇门:网络

假设你的智能体待在一个完美的微虚拟机里,手里握着真实的API密钥,还能连上开放互联网。没有网络隔离,一条注入的指令加一次对外请求,密钥就没了。每一堵墙都守住了,代码却从正门走了出去。

出口管控像一间收发室:只投递到白名单上的地址,比如pypi.org、github.com,其余一律拒绝。它还能泄露吗?如果代码读到了你的数据,又能触达某个被允许的主机,答案是能。所以白名单要尽可能短,DNS也要一并锁死。

密钥则要留在屋外。沙箱里拿到的是占位符,收发室在请求出门时换上真凭证:沙箱内是Authorization: Bearer PLACEHOLDER,出门后变成Authorization: Bearer sk-live-...。注入的代码可以整天读那个占位符,离开这台机器它一文不值。当然,它仍能借收发室使用真密钥,所以白名单还是得短。

有人真这么建吗?Claude Code的沙箱是一个例子。它默认关闭,用/sandbox开启。在Linux上它用bubblewrap,也就是容器那套命名空间技巧;在macOS上用Seatbelt,苹果内置的沙箱。命令没有直接出口,宿主代理会拿一个初始为空的白名单核对每个域名;开启掩码后,代理会把占位符密钥换成真密钥。它自己的文档说,这能降低风险,但不是完整的隔离边界。面对陌生人的代码,继续往上爬。

回到最初的问题:那个智能体逃出沙箱了吗?问两件事就够了——它是墙还是帘子?哪扇门开着?