Rust 安全响应团队披露了一个影响所有 Cargo 版本的漏洞——CVE-2026-5223。恶意构造的 crate tarball 可以利用符号链接(symlink)绕过 Cargo 的目录保护,把文件提取到自身缓存目录的上一层,从而覆写同一注册源下其他 crate 的源码缓存。如果你只使用 crates.io,不受影响;但依赖第三方注册源(私有 registry、企业内部源、镜像站)的团队需要立刻关注。
缓存结构与攻击路径
Cargo 构建一个 crate 时,会将其 tarball 解压到本地缓存目录 ~/.cargo/registry/src/ 下,后续构建直接复用这份源码,不再重新下载。Cargo 有防护机制,防止解压时文件逃逸到缓存目录之外。
问题在于:符号链接可以突破这层防护的边界。攻击者在一个 tarball 中放入指向上一层目录的符号链接,解压后该链接指向的是同一注册源下其他 crate 的缓存位置。随后 tarball 中再包含一个同名文件,Cargo 会顺着符号链接将文件写入目标 crate 的缓存——源码被静默替换。
举个简化的例子,假设注册源的缓存布局如下:
~/.cargo/registry/src/my-registry-abc123/
├── crate-a-1.0.0/ ← 被攻击目标
│ └── src/lib.rs
└── crate-b-1.0.0/ ← 恶意 crate
│ ├── evil_symlink -> ../crate-a-1.0.0/
│ └── evil_symlink/src/lib.rs ← 覆写内容
解压 crate-b 时,Cargo 沿符号链接将 lib.rs 写入 crate-a 的缓存,此后任何依赖 crate-a 的构建都在使用被篡改的代码。
crates.io 为什么没事
crates.io 在上传阶段就禁止包含任何符号链接的 crate,从源头堵死了这条路径。所以只消费 crates.io 的项目不受此漏洞影响。
风险集中在第三方注册源——私有 registry、企业内部源、社区镜像等。这些源如果对上传内容不做符号链接检查,攻击者就有机会构造恶意 tarball。值得注意的是,cargo package 和 cargo publish 本身不会在打包时添加符号链接,所以正常发布的 crate 不会携带链接。风险来自故意构造的恶意包。
修复时间线与版本范围
所有 Rust 1.96.0 之前版本的 Cargo 都受影响。
修复方案:Rust 1.96.0(计划 2026 年 5 月 28 日发布)中的 Cargo 将在解压时直接拒绝任何符号链接,不再区分来源是 crates.io 还是第三方注册源。这是最干净的修复——既然正常流程不会产生符号链接,那就一律拒绝。
无法立即升级到 1.96.0 的团队,官方建议两条缓解措施:
- 审计注册源中已有 crate 的内容,检查是否包含符号链接。
- 如果注册源支持配置,开启拒绝符号链接的选项。
立刻可以做的事:排查与缓解
下面给出几个可以直接运行的检查命令,帮助你评估当前环境的风险。
检查项目是否依赖第三方注册源
查看 .cargo/config.toml 或 .cargo/config 中的 registry 配置:
# 搜索所有 Cargo 配置文件中的第三方注册源定义
grep -r "registry" .cargo/config.toml .cargo/config 2>/dev/null || echo "未找到本地配置"
# 同时检查 Cargo.lock 中是否有来自非 crates.io 的依赖
grep -E 'source = "registry\+[^crates\.io]' Cargo.lock 2>/dev/null || echo "Cargo.lock 中无第三方注册源依赖"
如果以上输出为空或只出现 crates.io 相关内容,你目前不受此漏洞影响。
扫描本地缓存中的符号链接
# 扫描 ~/.cargo/registry/src 下所有符号链接
find ~/.cargo/registry/src -type l -ls 2>/dev/null
# 如果只想看第三方注册源的缓存(排除 crates.io),先确认目录名前缀:
ls ~/.cargo/registry/src/ | grep -v crates.io
# 然后针对特定目录扫描
find ~/.cargo/registry/src/<your-registry-hash> -type l -ls 2>/dev/null
输出为空说明当前缓存中没有符号链接,但这不代表未来下载的包也安全——除非注册源本身做了拦截。
扫描注册源上已发布的 crate tarball
如果你运营私有注册源,可以批量检查已存储的 tarball:
# 假设注册源的 crate 存储目录为 /srv/registry/crates
# 列出所有 tarball 中包含符号链接的条目
for tarball in /srv/registry/crates/*.crate; do
if tar tf "$tarball" | grep -qE '->'; then
echo "警告: $tarball 包含符号链接"
tar tf "$tarball" | grep '->'
fi
done
tar tf 列出条目时,符号链接会显示为 link_name -> target 的格式,用 -> 过滤即可。
为私有注册源添加上传校验
如果你的私有注册源有上传 API,在接收 tarball 时增加校验逻辑。以下是一个 Python 示例,可在 CI 或服务端部署:
#!/usr/bin/env python3
"""检查 crate tarball 是否包含符号链接,拒绝不安全的包上传"""
import tarfile
import sys
def check_crate_tarball(path: str) -> bool:
with tarfile.open(path, "r:gz") as tf:
for member in tf.getmembers():
if member.issym() or member.islnk():
print(f"拒绝: {path} 包含符号链接: {member.name} -> {member.linkname}")
return False
print(f"通过: {path} 无符号链接")
return True
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python check_crate.py <tarball.crate> [tarball2.crate ...]")
sys.exit(1)
all_ok = True
for path in sys.argv[1:]:
if not check_crate_tarball(path):
all_ok = False
sys.exit(0 if all_ok else 1)
将此脚本集成到注册源的上传流程或 CI 的 cargo publish 前置步骤中,可以从源头拦截恶意构造。
采取行动的优先级
| 场景 | 风险等级 | 建议动作 |
|---|---|---|
| 仅使用 crates.io | 无 | 无需操作,但升级到 1.96.0 仍是好习惯 |
| 使用第三方注册源,可升级 Rust | 中 | 5 月 28 日后升级到 1.96.0 |
| 使用第三方注册源,短期内无法升级 | 中 | 立即扫描缓存与注册源,添加上传校验 |
| 运营私有注册源 | 中 | 部署 tarball 符号链接检查,配置拒绝策略 |
这个漏洞的攻击前提是攻击者能在第三方注册源上发布恶意 crate——对于有准入控制的私有源,实际风险取决于你的发布审核流程是否严格。但缓存覆写的隐蔽性很高(被篡改的代码只在本地缓存中,Cargo.lock 和源码仓库看不出异常),一旦被利用很难追踪。建议在 Rust 1.96.0 发布前做好排查,发布后尽快升级。