前言

这段时间做的产品里,有运行用户脚本的需求。 于是今天写一篇文章,简单介绍一下一把梭、低成本实现 Python 沙箱的思路。

关于 CPython Interpreter

当你在寻找Python沙箱的相关资料时,一定会看到这样的描述

CPython由于其特性无法实现沙箱,与之相比,Pypy具有沙箱特性的支持。

然而 Python 的生态基本是建立于 CPython 之上的,那么如果我们想要在 CPython 上运行 untrusted user code 应该怎么办呢?

我们可以从 OS 的角度来入手。

Seccomp

Seccomp 是Linux的安全计算特性之一

在某一线程启用 seccomp 后,该线程之后除了 write, read, sigreturn, _exit 这四个 syscall,调用任何 syscall 都将导致 SIGKILL

setrlimit

setrlimit 也是 linux 特性之一,用于限制进程的资源消耗

与之用途类似的还有 CGroup,CGroup 功能更强大,但考虑到本文专注于一把梭,就不展开讨论了。

于是

Seccomp + setrlimit + 超时 Kill,就足够我们安全的运行任意Python代码了。

这其实也是参考了 docker 的部分内容,择出来了最必要的一部分来实现我们的需求。

但是

简单直接的 Seccomp STRICT MODE 对于 Python 来说不够用,使用BPF Filter模式允许 mmap, munmap 等 syscall 很有必要。

但即使我们通过 BPF Filter 允许了更多操作,这给我们带来的维护成本是非常高的,需要我们通读 CPython 的实现,或是通过跟 call stack 的形式,去进行 syscall 白名单的丰富,这显然跟本文一开始所提的一把梭、低成本实现思路有冲突。

所以,如果要提供一些多样的功能供沙箱内脚本使用,可以再单独跑一个 API Worker,让 user code worker 与之通信,这样就可以把复杂的功能通过 RPC 的形式分发给辅助进程,我们再在 RPC 一层做安全相关的数据校验和限制即可。

其他的一些细节

seccomp 和 rlimit 启用之前需要更多的安全环境配置,比如 chroot,drop privilege, 已经打开过的文件描述符的清理,Python module 尽可能不要import(因为 CPython 无法 de-import,只能尽可能隐藏),以及一些其他我也没能想到的。

终极解决方案

使用 docker