前言
这段时间做的产品里,有运行用户脚本的需求。 于是今天写一篇文章,简单介绍一下一把梭、低成本实现 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