超时不等于安全结束:Wasmtime 批量操作漏洞与取消后的状态一致性
一、已核验事实:先确认嵌入方式,再判断风险
项目公告发布于 2026-07-31 ;GitHub 已审核记录于 2026-10-02收录,关联 CVE-2026-104855。两份记录对版本范围一致:
| 分支 | 受影响版本 | 修复版本 |
|---|---|---|
| 46 | 46.0.0、46.0.1 | 46.0.2 |
| 47 | 47.0.0、47.0.1、47.0.2 | 47.0.3 |
**官方确认的机制与限制:**批量指令执行期间的 fuel/epoch 检查可能暴露中间状态。若嵌入者在回调中改变 Wasm 状态,或者在取消后继续使用同一 Store,可能出现失效指针、未初始化状态或崩溃。修复恢复为相关操作开始前检查预算,而不是操作中途检查。仅在回调中访问 Store<T> 的宿主数据等安全使用模式,公告另有说明;不能将所有 Wasmtime 部署都视为可利用。
官方评级为 Low、CVSS 4.0 为 2.0。本文没有证据把它描述成通用远程代码执行或已确认的沙箱逃逸。所核验来源也未确认在野攻击。
二、背景:限制计算时间之后,还要限制可见状态
插件、规则引擎和多租户计算平台常用执行预算防止任务长期占用工作线程。开发者容易把任务视为一个黑盒:成功就提交结果,超时就返回错误。
但真实运行时会维护内存、表、引用和分配器状态。某个操作可能先调整容量,再填充数据,最后更新其他索引。若在中间阶段发生回调或取消,外部代码可能接触到尚未满足完整约束的对象。
**工程分析:**这里的审查对象应从"操作有没有超时"转向"允许在哪些状态暂停"。一个系统可能同时拥有出色的时间限制和错误的取消语义。测试只验证超时错误码,会遗漏第二类问题。
可以用三种状态描述通用契约:可用、更新中、不可再用。成功后回到可用;失败后要么恢复旧状态,要么明确转入不可再用。最危险的情况是对象处于更新中,却被调用者当作可用对象再次执行。
三、为什么回调特别值得审计
回调将控制权交给外部代码。即使没有线程并发,调用者也可能修改共享状态,使调用前保存的地址、索引或引用失效。这是重入与状态变化的问题,不应只靠"没有多线程"排除风险。
审查一个回调点时,可以依次追问:
- 回调前保存了哪些引用和派生值?
- 回调是否允许分配、扩容、回收或再次进入执行器?
- 回调返回后哪些值需要重新获取?
- 如果回调抛错,内部状态如何提交或回滚?
- 上层是否会把失败对象重新放回池中?
这些是通用工程问题,不是对 Wasmtime 未公开实现的补充披露。对产品本身,应以官方版本升级为主,而不是自行删除预算检查。
四、安全实验:取消后不暴露半成品
以下 Python 模型不加载 WebAssembly、不操作真实指针,只演示"暂存后提交"与"先改共享状态"的差别。取消点是固定的,不会消耗大量资源。
python
class Cancelled(Exception):
pass
def update_in_place(state):
state.extend([None, None])
state[-2] = 7
raise Cancelled()
def update_transactionally(state):
staged = list(state)
staged.extend([None, None])
staged[-2] = 7
raise Cancelled() # 尚未提交,原状态不变
# 正常路径应在全部校验后执行 state[:] = staged
broken, preserved = [1], [1]
for fn, value in [(update_in_place, broken),
(update_transactionally, preserved)]:
try:
fn(value)
except Cancelled:
pass
assert None in broken
assert preserved == [1]
print("2 checks passed")
该模型不是 Wasmtime 补丁。暂存复制并非所有运行时都能接受的方案:它有内存和性能成本。另一类设计是将失败对象标记为不可复用,并销毁其执行上下文。需要哪种保证,应写进 API 契约,而不是让调用者猜测。
五、影响范围与检测思路
先从依赖和构建产物确认实际 Wasmtime 版本,再阅读宿主集成代码。只在源仓库搜索到依赖名,无法证明生产代码采用了受影响使用模式。
| 检查位置 | 需要确认的性质 |
|---|---|
| epoch 回调 | 是否改变 Wasm 内存、表或相关运行时状态 |
| 超时处理 | 是否继续在同一 Store 中执行后续 Wasm |
| 对象池 | 失败对象是否被无条件回收复用 |
| 批量操作 | 是否存在很大的输入和性能预算要求 |
| 错误恢复测试 | 是否验证状态,而不只是错误码 |
这些检查不需要向生产系统提交恶意模块。可以在测试环境使用正常小数据,主动注入取消,观察对象是否按预期销毁或复位。日志应记录执行上下文标识、取消原因及复用决策,不记录敏感内存内容。
六、研发与安全团队行动清单
**立即:**受影响分支升级到 46.0.2、47.0.3 或供应方确认包含修复的后续版本。对存在公告所述回调修改或取消后恢复模式的应用,不将简单配置切换宣传为完整补丁。
**近期:**为失败路径补充状态断言。区分正常退出、宿主错误、预算耗尽和取消,分别定义 Store 生命周期。升级后验证长批量操作的响应时间,因为修复改变了相关操作中的抢占行为。
**持续:**为回调接口编写允许操作清单;在代码审查中标出调用前保存、调用后复用的引用。将"失败后对象能否复用"作为运行时升级验收项,覆盖嵌入层而不只测试库自身。
总结
执行预算控制资源,取消契约保护状态。安全运行时需要同时满足这两种要求。一次超时应当结束任务,但不应该留下一个看似正常、实际处于半完成状态的对象。