CVE-2026-40344:Snowball 自动解包认证绕过

Snowball unsigned-trailer 请求可以在完成认证前进入解包器;修复把 SigV4 验证前移到任何 tar 字节之前。

状态: 已发布
首个包含版本: RELEASE.2026-04-17T00-00-00Z
GitHub Advisory: GHSA-9c4q-hq6p-c237

Snowball PutObjectExtractHandler 漏掉了 streaming unsigned-trailer auth case。伪造 signature 的 tar stream 可以在认证完成前进入 untar(),而一次请求又能扇出为多个对象写入。最终修复在任何 tar 字节进入解包器之前,初始化正确 reader、处理 decoded length 并完成 SigV4 验证。

编号为什么变过

修复时正式 CVE 尚未分配,commit subject 使用了临时的 fake CVE-2026-40028。正式编号后来确定为 CVE-2026-40344。历史 commit 没有重写;公开 advisory 与本文一律使用正式编号。

从一次认证遗漏到批量对象写入

入口是 Snowball / PutObjectExtract 自动解包。请求采用 unsigned-trailer streaming,而旧 handler 没有像普通 PUT 一样覆盖该 auth type。

危险不只在于一个请求被错误授权。tar stream 一旦进入 untar(),单个请求可以创建多个攻击者指定的对象。认证错误因此被放大成批量写入问题。

最终不变量:失败时解包器必须看到零字节

修复过程中最关键的一句话是:

如果认证最终失败,untar() 必须看到零字节。

这直接排除了“先解包,认证失败后再回滚”的方案。对象写入会经过多条路径,要证明回滚完整远比证明输入从未越过边界困难。正确收口点只能在数据流进入解包器之前。

实现

最终改动包括:

  • 识别 authTypeStreamingUnsignedTrailer
  • 读取 X-Amz-Decoded-Content-Length
  • 使用 newUnsignedV4ChunkedReader()
  • 在进入 untar() 前执行完整 SigV4 request verification;
  • 保留合法 signed Snowball 与 CRC32 trailer flow。

验证

历史 commit 与会话记录覆盖:

  • forged-signature Snowball unsigned-trailer 被拒绝;
  • non-public bucket 的 anonymous Snowball 被拒绝;
  • 合法签名与 trailing CRC32 可以正常解包;
  • vulnerable parent 与 patched tree 的红绿对照;
  • containerized before/after smoke。

公开修复提交为 b50ab58。本次博客整理没有重新运行容器测试。

兼容性与残余风险

  • 过去依赖实际未验证的 unsigned-trailer Snowball 组合的客户端,升级后会失败;
  • 认证已经前移,但 tar 内容路径、归档大小与对象数量限制仍是独立安全面;
  • Snowball 与普通 unsigned-trailer 现在共享 reader,未来修改必须同时回归两条路径。

这个事件的重点不是多加了一次 if,而是把认证决定移动到真正的放大边界之前。

最后修改 August 2, 2026: init commit (8338d5b)