在上一篇中,我们确定了“进程级沙箱”的路线。但摆在面前的第一个技术难题是:如何在 Python 进程启动的那一刻,强行插入我们的安全代码?
我们需要在用户代码执行之前,完成 Chroot 和 Seccomp 的设置。如果等到用户代码开始跑了再限制,黄花菜都凉了。
尝试一:直接修改解释器源码?
最硬核的办法是下载 CPython 源码,在 main 函数里加几行代码,重新编译一个 safe-python。
但这太蠢了。
- 维护噩梦:每次 Python 发新版,我们都得重新打补丁、编译。
- 不通用:那 Node.js 怎么办?Java 怎么办?难道都要改源码?
我们需要一种非侵入式的方案。
尝试二:LD_PRELOAD?
Linux 有个黑魔法叫 LD_PRELOAD,可以在程序运行前预加载动态库。我们试着写了个 C 的 .so,劫持 __libc_start_main。
但这在 Go 里实现起来很麻烦,而且容易跟 Python 自身的初始化逻辑冲突。我们希望核心逻辑还是用 Go 写,毕竟 Go 处理系统调用比 C 舒服多了。
最终方案:Python 的“内鬼” —— ctypes
后来我们灵光一闪:Python 不是自带 ctypes 吗?它能加载 C 的动态库。
如果我们把 Go 代码编译成一个 C 兼容的动态库 (.so),然后写一个极简的 Python 脚本(我们叫它 prescript.py),让这个脚本先加载 .so,执行安全锁定,然后再执行用户代码,岂不是完美?
第一步:把 Go 伪装成 C
Go 的 c-shared 构建模式简直是神器。我们只需要在 Go 函数前加上 //export,它就能被外部调用。
看看我们的 cmd/lib/python/main.go,它长得一点都不像普通的 Go 程序:
package main
import "C" // 必须引入这个,不然 CGO 不工作
import (
"github.com/leiguorui/go-sandbox/internal/core/lib/python"
)
//export SandboxSeccomp
// 这一行注释告诉编译器:把这个函数暴露给 C!
func SandboxSeccomp(uid int, gid int, enable_network bool) {
// 这里面是真正的 Go 逻辑:Chroot, Seccomp 等
python.InitSeccomp(uid, gid, enable_network)
}
func main() {} // 必须有个空的 main
编译一下:
go build -o python.so -buildmode=c-shared cmd/lib/python/main.go
现在,我们有了一个 python.so,在 Python 眼里,它就是个普通的 C 库。
第二步:编写引导脚本
接下来,我们需要一个 Python 脚本来充当“内鬼”。这就是 prescript.py 的由来。
我们在开发时遇到的一个坑是:如何把用户代码传进去?
直接写文件?不安全,容易被用户读取。
最后我们决定:直接把加密后的用户代码硬编码在脚本里。
看看这个脚本的雏形(为了方便理解,我简化了部分逻辑):
import ctypes
from base64 import b64decode
# 1. 召唤“特洛伊木马”
# 加载我们刚才编译的 python.so
lib = ctypes.CDLL("./python.so")
# 2. 告诉 ctypes 这个函数的参数类型
# 这一步很关键,搞错了类型会导致内存崩溃
lib.SandboxSeccomp.argtypes = [ctypes.c_uint32, ctypes.c_uint32, ctypes.c_bool]
# 3. 【高光时刻】执行锁定!
# 这行代码执行完,当前进程就被关进笼子了
# 之后再想访问 /etc/passwd?没门!
lib.SandboxSeccomp(uid, gid, enable_network)
# 4. 图穷匕见:执行用户代码
# 此时环境已经安全,我们可以放心地解密并运行用户的代码了
user_code = b64decode("{{code}}") # 这里会被 Go Server 替换成真正的代码
exec(user_code)
总结
通过 ctypes + c-shared,我们成功地在 Python 解释器内部植入了一个 Go 编写的“安全栓”。
这种方法的妙处在于:
- 零修改:直接使用系统自带的 Python。
- 跨语言:同样的思路,Node.js 可以用
ffi-napi做,Java 可以用 JNI 做。
解决了“怎么进”的问题,下一篇我们来聊聊“进了之后做什么”:如何用 Seccomp 编织一张逃不出去的网。