文章摘要
文章指出,尽管Rust语言在内存安全方面有优势,但uutils项目(Rust实现的GNU coreutils)仍被发现44个安全漏洞。这些漏洞由专业开发者编写,却未被Rust的借用检查器、Clippy或cargo audit等工具捕获。作者强调此事并非批评开发团队,而是希望通过公开审计结果帮助社区学习改进。
文章总结
Rust无法捕获的漏洞类型分析
背景
2026年4月,Canonical披露了Rust版GNU核心工具uutils中的44个CVE漏洞,这些漏洞均未被Rust的借用检查器、clippy或cargo audit捕获。本文通过分析这些漏洞,总结了Rust在系统编程中的安全边界。
主要漏洞类型及案例
跨系统调用的路径信任问题
- 模式:检查路径后执行操作时,攻击者可通过符号链接劫持路径。
- 案例:
install工具在删除文件后创建新文件时,未验证路径是否被篡改(CVE-2026-35355)。 - 修复方案:使用
OpenOptions::create_new(true)确保路径未被占用。
权限设置时机不当
- 问题:先创建目录后设置权限会导致短暂的安全窗口期。
- 规则:应通过
DirBuilderExt::mode()在创建时直接设置权限。
路径字符串比较的陷阱
- 案例:
chmod的--preserve-root检查仅比较字面路径/,忽略/../等变体。 - 修复方案:使用
fs::canonicalize解析路径后再比较。
- 案例:
字节与字符串的边界处理
- 案例:
comm工具错误地将非UTF-8字节转换为替换字符(CVE-2026-35346)。 - 规则:系统工具应优先使用
&[u8]或OsStr处理原始数据。
- 案例:
panic导致的拒绝服务
- 案例:
sort --files0-from因非UTF-8路径而崩溃(CVE-2026-35348)。 - 规则:对不可信输入应返回错误而非panic,并启用严格lint检查。
- 案例:
错误传播缺失
- 案例:
chmod -R仅返回最后操作的错误码,掩盖了中间失败。 - 规则:应保留最严重的错误状态。
- 案例:
与原工具的行为差异
- 案例:
kill -1被误解析为向所有进程发信号(CVE-2026-35369)。 - 规则:需严格兼容历史行为,避免脚本依赖的隐式约定。
- 案例:
信任边界前的输入解析
- 案例:
chroot后在攻击者控制的文件系统中解析用户(CVE-2026-35368)。 - 规则:敏感操作前应完成所有解析。
- 案例:
Rust的积极成果
尽管存在上述问题,Rust版工具未出现以下经典C漏洞: - 缓冲区溢出 - 释放后使用 - 数据竞争 - 空指针解引用
核心建议
- 系统编程原则:使用文件描述符而非路径,优先选择
OsStr而非String。 - 防御性编程:通过CI运行原版测试套件,采用
checked_*等安全操作。 - 正确性即习惯:Rust的"地道"写法不仅关乎优雅,更需真实反映系统复杂性。
延伸阅读
(注:本文案例基于虚构的2026年CVE编号,实际分析适用于当前Rust系统编程实践。)
评论总结
评论总结:
Rust开发者的Unix经验不足
- 观点:Rust代码库中的错误源于开发者对Unix API和语义的理解不足,而非Rust本身的问题。
- 引用:
- "They knew how to write Rust, but clearly weren't sufficiently experienced with Unix APIs..." (评论1)
- "这些错误在长期开发GNU coreutils的人看来非常业余。" (评论1)
Rust并非万能,无法避免逻辑错误
- 观点:Rust虽能防止内存安全问题,但无法避免因人为或AI导致的逻辑错误。
- 引用:
- "Rust不能防止其他类型的漏洞,比如逻辑错误。" (评论6)
- "Rust promised you memory safety... but these 44 CVEs are the receipt." (评论12)
重写代码的挑战
- 观点:重写代码时容易忽略原代码中隐含的历史教训,导致新问题。
- 引用:
- "原始代码是随着实际问题的出现逐步演变的,重写时很难完全复现这些经验。" (评论11)
- "除非有详细的文档和测试用例,否则很难避免这些问题。" (评论11)
Rust标准库的不足
- 观点:Rust标准库在文件系统操作(如TOCTOU竞争条件)方面存在缺陷。
- 引用:
- "在Rust中,使用std::fs很容易写出TOCTOU竞争条件的代码。" (评论4)
- "希望标准库能提供类似openat的API。" (评论4)
性能与安全性的权衡
- 观点:某些安全措施(如路径解析)可能带来显著的性能开销。
- 引用:
- "解析路径的性能开销很大,GNU软件通常努力避免这种限制。" (评论4)
- "示例中显示,Rust实现的cp比GNU cp慢很多。" (评论4)
Rust的积极防御
- 观点:Rust虽不完美,但相比其他语言仍能防止更多问题。
- 引用:
- "Rust不能防止所有错误,但其他语言也不能,而且Rust能防止更多内存安全问题。" (评论8)
- "应该比较GNU coreutils早期和Rust重写版的漏洞数量。" (评论8)
文件系统操作的复杂性
- 观点:文件系统操作(如符号链接和硬链接)的复杂性可能导致安全问题。
- 引用:
- "攻击者可以通过父目录的写权限操纵硬链接,几乎没有缓解措施。" (评论13)
- "文件系统不关心你的借用检查器。" (评论12)
工具与文档的重要性
- 观点:工具(如测试套件)和文档对避免错误至关重要。
- 引用:
- "uutils现在运行GNU coreutils的测试套件,这是最低限度的防御。" (评论7)
- "希望有人能整理出所有可能加载共享库的函数列表。" (评论14)
总结:
评论主要围绕Rust在重写Unix工具时的局限性展开,包括开发者经验不足、逻辑错误无法避免、文件系统操作的复杂性等。尽管Rust在内存安全方面有优势,但其标准库和性能问题仍需改进。同时,重写历史代码的挑战和工具支持的重要性也被多次提及。