如何 WASM 沙箱?
解读
面试官抛出“如何 WASM 沙箱?”并不是想听“把 Rust 编译成 .wasm 丢进浏览器就行”。在国内落地场景里,沙箱=可控、可审计、可隔离、可限权。
考点集中在:
- 编译期如何把“不安全”的 Rust 代码关进笼子;
- 链接期如何生成单实例、无宿主符号泄露的模块;
- 运行时如何精细配额(CPU、内存、syscall)、如何做** capability-based 授权**;
- 国产合规要求:国密算法、等保 2.0 访问控制、信创环境(龙芯/鲲鹏 + 麒麟 V10)适配。
一句话:让 .wasm 成为“不可逃逸、不可越权、不可耗尽宿主资源”的沙箱进程。
知识点
-
Rust 侧
wasm32-wasitarget:唯一真子集,禁用std::thread、std::time、std::fs等宿主敏感 API;#![no_std] + alloc+ 自定义panic_handler:把标准库拿掉,杜绝隐式系统调用;wasm-bindgen只导出最小接口,禁止#[wasm_bindgen(start)],防止模块自启动;- 使用
wee_alloc或lol_alloc做内存配额可感知的分配器; - 在
build.rs里用cargo auditable生成 SBOM,满足国内审计要求。
-
编译与链接
RUSTFLAGS="-C link-arg=--no-entry -C link-arg=--export-dynamic"生成可重入、无 _start 的纯 reactor 模型;wasm-opt -O4 --strip-producers --strip-debug裁剪自定义段,防止泄露编译路径;- 用
wasm2wat人工审计导入表,确保只有 wasi_snapshot_preview1 白名单函数; - 若需国密,把
libsm编译成wasm32-wasi静态库,禁用 x86_64 汇编路径。
-
宿主侧(Rust 或 Go)
- 选型:
wasmtime≥ 15.0(国内镜像源:rsproxy.cn),禁用 JIT 编译(Config::strategy(Strategy::Cranelift)即可,信创环境无 Intel CET 问题); - 资源配额:
–Store::limit_memory(8 * 64 * 1024)限制 512 KiB;
–Store::limit_fuel(1_000_000)配合consume_fuel做指令级计费; - 系统调用过滤:用
wasmtime_wasi::WasiCtxBuilder只挂接random_get、clock_time_get等最小白名单,fd_write重定向到宿主管道,禁止直接写文件系统; - capability 模型:把
wasmtime::InstancePre与国产 RBAC 微服务对接,每个租户一个 Store,实例间内存零共享; - 热升级:利用
wasmtime::Module::serialize预编译成 ELF 共享库,秒级冷启动 < 5 ms,满足金融行情场景。
- 选型:
-
国产合规加固
- 等保 2.0:在宿主层加
seccomp-bpf与selinux双保险,即便 wasmtime 被 RCE 也无法提权; - 国密 TLS:宿主与沙箱内分别使用
sm2/sm3/sm4,双向证书校验,满足银保监文件要求; - 日志:把
fuel_consumed、memory_growth打到Kafka -> 国密版 ELK,留存 ≥ 6 个月。
- 等保 2.0:在宿主层加
答案
“在 Rust 项目里,我通过四步实现 WASM 沙箱:
第一步,代码层用 wasm32-wasi target 并去掉标准库,只保留最小导出函数;自定义 #[global_allocator] 使用带配额回调的 wee_alloc,一旦超量立即 trap。
第二步,编译期用 wasm-opt 裁剪自定义段,关闭所有调试符号;用 wasm2wat 人工审计,确保导入表只有 wasi_snapshot_preview1 中的 random_get、clock_time_get 等 7 个安全函数。
第三步,宿主层用 wasmtime,关闭 JIT、开启 fuel 机制,内存上限 512 KiB,CPU 上限 100 万 fuel;系统调用通过 WasiCtxBuilder 白名单过滤,并把 fd_write 重定向到宿主管道,实现零文件系统访问。
第四步,合规加固:模块预编译成 ELF 共享库,启动时间 < 5 ms;宿主加 seccomp+selinux,日志实时入国密版 ELK,满足等保 2.0 与国密算法要求。
通过以上手段,.wasm 模块无法逃逸、无法越权、无法耗尽资源,形成生产级沙箱。”
拓展思考
- 多租户密度:一个
wasmtime::Engine共享,每个租户一个Store,内存池复用MemoryPool可把 1 GiB 内存切成 4 KiB 块,千实例常驻内存 < 50 MiB。 - 异步宿主:把
wasmtime::Store封装成tokio::task,fuel 耗尽时yield回 Tokio,实现毫秒级抢占,解决长尾任务卡死问题。 - 信创环境:在龙芯 3C5000 + 麒麟 V10 上,
wasmtime需关闭simd与unwind特性,用compiler_builtins-mem替代 LLVM memcpy,性能下降 15% 但可通过。 - 形式化验证:用
KLEE-wasm对 .wasm 做符号执行,证明无整数溢出与越界访问,输出报告可直接提交给央行金融认证中心(CFCC)审计。