Rust 借用检查器 11 年未响漏洞:P-critical 安全缺陷曝光

· 科技资讯

Rust 借用检查器 11 年未响漏洞:P-critical 安全缺陷曝光

Rust 最引以为傲的安全特性——借用检查器——被发现了从 2015 年(Rust 1.4)就存在的未响漏洞,多个变体在 11 年间从未被编译器捕获。这个发现像一颗石子投进了 Rust 社区看似平静的湖面,HN 上 649 分的讨论量说明开发者们既震惊又不安。

漏洞到底怎么了

核心问题出在”协变字段赋值”(covariant field assignment)上。简单来说:当你有一个泛型结构体,其类型参数带有 ‘static 生命周期约束,而你在 Drop 实现中访问的引用实际上并不满足 ‘static 要求时,编译器没有报错。代码能编译通过,运行时却能访问已经销毁的内存。

提交者 jsnell 构造了一个极简 PoC:一个包含 Option 的结构体,T 被标注为 ‘static + Send,Drop 实现中把里面的值推送到一个 static Mutex 保护的 Vec 里。编译器完全放行,但 ASan 立刻报出 stack-use-after-scope。更可怕的是,另一位贡献者 lcnr 发现了一个更简洁的版本,追溯发现这个漏洞从 Rust 1.4(2015 年 4 月)就存在了。

11 年漏洞存在时间1.4最早受影响的版本P-critical优先级标记

为什么 11 年没被发现

这是所有 Rust 开发者最关心的问题。借用检查器的核心逻辑建立在”类型统一”(unification)上,而统一算法在检查字段赋值时,没有验证类型参数的生命周期约束。这个设计决策在 2015 年可能是有意的简化——当时的类型系统还没有处理复杂协变场景的需求。

但 11 年间,Rust 的类型系统经历了多次重大演进:async trait、GAT(Generic Associated Types)、const generics……每一次扩展都增加了类型检查的复杂度,但借用检查器核心的这个盲区始终没有被触及。社区贡献者 theemathas 在 issue 里写了一句让人五味杂陈的话:”我的版本从 1.18.0(2017 年)就能编译。这到底是怎么没被发现的???”

漏洞触发路径 泛型结构体 T: ‘static + Send Drop 实现 访问非 ‘static 引用 编译器放行 未验证生命周期 运行时 UB use-after-scope 根因:类型统一算法的盲区 • 字段赋值时,统一算法不检查类型参数的生命周期边界 • 协变(covariant)位置上的引用赋值绕过了 ‘static 约束验证 • 多个独立变体已确认,影响范围待完整评估

对 Rust 生态意味着什么

首先要冷静:这是一个未响漏洞,不是内存安全漏洞的”回归”。Rust 的内存安全保证建立在”要么类型系统正确拒绝,要么运行时不会访问非法内存”这个前提上。这个漏洞说明类型系统在特定边界条件下拒绝了本该拒绝的代码,但不会导致数据竞争或任意内存读写——ASan 报的是 use-after-scope,不是 use-after-free。

但”安全”和”完美”是两回事。Rust 社区一直用”内存安全”作为核心卖点,而这次事件暴露了一个事实:类型系统的正确性证明远没有想象中那么完整。借用检查器有数万行代码,处理了无数边界情况,但总有一些组合是设计时没有预见到的。

更深层的问题是:在 Rust 被广泛用于操作系统、浏览器引擎、嵌入式系统的关键路径上,这种漏洞的潜在影响需要被严肃对待。虽然 PoC 需要刻意构造,但”需要刻意构造”不等于”不会在真实代码中偶然触发”。

值得关注的是 Rust 团队的响应速度。issue 在提交后几小时内就被标记为 P-critical,types 团队已经介入。推测修复方案会涉及类型统一算法的修改,可能需要引入新的检查路径。对于正在使用 Rust 的生产系统,建议关注后续的安全公告,特别是如果你的代码大量使用了带生命周期约束的泛型结构体和自定义 Drop 实现。

核心观点:Rust 的”内存安全”承诺不是被推翻了,而是被精确化了——它保证的是”编译器接受的所有代码都是安全的”,而不是”类型系统不会接受不安全的代码”。这两个表述的差别,就是这次 11 年漏洞的本质。

— END —

LC 智趣厅 · 科技与生活的交点

ihygg.cn

Scroll to Top
微信公众号:LC智趣厅

扫码关注微信公众号
LC智趣厅