Yuzhe's Blog

yuzhes

WASM vs seccomp:为代码评测器测量沙箱启动开销

上周我们发布了 sandbox_exec——一个用 seccomp-bpf 隔离学生代码的 224 行 C 程序。当时的诚实答案是:“WASM 的安全模型更干净,但 Python 生态还没准备好。”

这周我们量化了”还没准备好”到底意味着多少毫秒。答案比预想的更有意思。

测试环境

阶段一:冷启动开销

最基础的问题:沙箱启动空载代码需要多久?

环境均值P95
WASM(JIT)9.79ms10.86ms
WASM(AOT 预编译)9.25ms10.14ms
Python(无沙箱)14.71ms15.29ms

WASM 比 Python 本身启动更快。这是反直觉的结论——大家默认”虚拟机=慢”,但 Wasmtime 的初始化比 CPython 解释器启动更紧凑。

这 10ms 的拆解:

0–2ms:   fork() + exec(wasmtime)
2–7ms:   Wasmtime 运行时初始化
         ├── 命令行解析
         ├── 配置加载
         └── WASI 环境准备
7–9ms:   WASM 模块处理
         ├── 文件读取
         ├── 类型校验
         └── JIT 编译
9–10ms:  执行 + 清理

绝大多数时间花在 Wasmtime 自身的初始化上,不是模块解析或 JIT。

阶段二:模块大小影响

模块大小耗时增量
~100B9.58msbaseline
~4KB9.68ms+0.1ms
~40KB10.97ms+1.4ms

模块大小增加 400 倍,只多花 1.4ms。初始化开销主导一切。

阶段三:计算性能

这是 WASM JIT 优势开始显现的地方:

计算量WASMPython加速比
fib(10)10.06ms15.12ms1.5x
fib(20)9.63ms16.80ms1.7x
fib(25)10.77ms25.94ms2.4x
fib(30)15.91ms128.97ms8.1x

fib(30) 时,WASM 总耗时约 16ms(10ms 启动 + 6ms 计算),Python 需要 129ms。WASM 整体超越 Python 的临界点大约在 fib(20–25)——大概是计算开销超过启动开销的那个位置。

对于评测算法题的评测器来说,这个差距是真实的。

阶段四:I/O 开销

操作WASMPython
1 次 fd_write10.16ms15.15ms
100 次 fd_write9.97ms15.23ms

100 次写入和 1 次写入耗时几乎相同。启动开销完全掩盖了 I/O 本身的开销,WASI I/O 额外成本可以忽略不计。

阶段五:内存分配

内存大小耗时
64KB9.62ms
1MB9.86ms
4MB10.04ms
16MB9.74ms

WASM 使用懒惰分配。声明 16MB 内存几乎不影响启动时间。

阶段六:安全特性的开销

配置耗时备注
无限制9.58msbaseline
+fuel(指令计数)7.94ms反而更快
+内存限制7.76ms反而更快
+目录预授权10.50ms+0.9ms
全部开启7.91ms

添加 fuel 和内存限制反而比不加更快——可能触发了某条优化路径。唯一有可测量开销的是目录预授权(+0.9ms 用于文件系统能力配置)。

安全特性的开销是负数。 这很罕见。

安全模型的本质差异

性能之外,安全模型的对比更鲜明:

维度sandbox_execWASM
隔离级别进程虚拟机
内存隔离共享地址空间线性内存(硬边界)
syscall 控制seccomp 白名单根本不存在 syscall
文件系统需外部清理能力授权
网络seccomp 阻断默认不存在

WASM 不过滤 syscall——它根本没有 syscall。WASI 下的 WASM 模块无法调用 socket()ptrace()io_uring_setup(),因为在沙箱内部没有调用这些东西的机制。

这比 seccomp 的白名单是本质上更强的保证。seccomp 的逻辑是”阻断这 62 个 syscall”,WASM 的逻辑是”没有 syscall 这个概念”。两者之间的攻击面差距是性质上的,不是数量上的。

为什么我们现在还没用 WASM

安全模型更强。CPU 密集型代码性能更强。启动开销相当。

问题在 Python:

Python WASM 运行时大小C 扩展结论
MicroPython370KB标准库受限
RustPython~5MB部分不完整
Pyodide~15MB浏览器专用,500ms+ 启动

评测器需要 numpy、scipy 和任意 C 扩展。Pyodide 支持这些,但依赖浏览器 JS 引擎——无法在 Wasmtime 下运行。MicroPython 和 RustPython 不支持完整的科学计算生态。

这不是性能问题,是生态问题。WASM Python 工具链进化很快,但”运行任意学生 numpy 代码”这个需求,它还没准备好。

路线图

现在:      sandbox_exec(seccomp + rlimit)
           └── 完整 Python + C 扩展
           └── ~1.5ms 沙箱 + ~15ms Python 启动
           └── 62 个阻断 syscall

1–2 年:    WASM 用于非 Python 语言
           └── JS/Rust/Go 学生代码 → WASM 直接运行
           └── 更强安全性,相当的性能

2–3 年:    WASM Python 生态成熟后迁移
           └── Component Model + WASI Preview 2
           └── 混合架构:Python → sandbox_exec,其他 → WASM

混合架构大概率是最终形态:Python 继续用 seccomp(C 扩展支持不可妥协),其他语言用 WASM(生态已经成熟)。

数字总结

如果你在为类似场景评估 WASM:

10ms 的启动开销不是阻塞因素。Python 生态才是。


性能测试由明石(CTO)主导。所有数据:Wasmtime v42.0.1,macOS arm64,50 次运行均值。