找回密码
立即注册
搜索
发回帖 发新帖

6507

积分

0

好友

822

主题
发表于 6 小时前 | 查看: 4| 回复: 0

写 Rust 时,我们经常依赖一个判断:只要使用安全接口,编译器就会阻止悬垂引用。但如果编译器自己漏了一道检查呢?

10 月 13 日,Rust 仓库收到了一份特别的 bug 报告:一段完全没有 unsafe 的代码,竟然可以把局部引用保存到作用域之外,并在原数据销毁后继续读取。

这个问题被标记为 I-unsound,也就是类型系统的安全性漏洞。讨论中的复现变体,甚至可以追溯到 Rust 1.4。

一句 'static,为什么没拦住它?

先看一个简化版:

struct Q<T: 'static>(Option<T>);

impl<T: 'static> Drop for Q<T> {
    fn drop(&mut self) {
        // 这里可以依赖 T: 'static
    }
}

fn main() {
    let text = String::from("hello");
    let mut q = Q(None);

    q.0 = Some(&text); // 本应被拒绝
}

Q 明确要求:T 必须满足 'static。

这里容易产生一个误解:T: 'static 并不是要求这个值永远存在,而是要求它不包含有效期不足的借用。

例如,局部创建的 String 拥有自己的数据,所以可以满足 T: 'static;但指向局部变量的 &String,通常不能。因此,把 &text 填进这个结构体,本应收到编译错误。

实际情况却是:受影响的编译器接受了这段代码。结构体声明的要求,就这样被绕过去了。

协变:安全的规则,遇到了漏掉的检查

issue 标题中也提到了一个词:协变。如果不是特别了解它的定义,那就先从一个简单的直觉开始:长期有效的引用,可以按较短的期限使用。

比如,一段永久有效的字符串,当然也可以传给一个只需要临时读取它的函数:

fn show<'a>(text: &'a str) {
    println!("{text}");
}

let text: &'static str = "hello";
show(text);

这个过程没有延长任何引用的生命周期,因此是安全的。共享引用的协变,让 Rust 能灵活处理这种情况。

但类型关系能够调整,不代表类型自己的约束可以消失。对于 Q<T: 'static>,无论编译器如何推导、匹配类型,都必须确保最终的 T 仍然满足 'static。

这个漏洞暴露的,正是这两件事之间的检查缺口:字段接收了短生命周期引用,析构函数却仍然相信结构体声明的生命周期承诺。

真正让引用“逃出去”的,是 Drop

仅仅把引用放进去,还不足以看清后果。关键在于:Drop 可以做什么?

在原始复现中,析构函数会取出字段内容,将它装箱,保存到一个全局容器。于是发生了这样的过程:

  1. 创建局部字符串。
  2. 把字符串的引用写入 q。
  3. 离开作用域,q 的析构函数将引用保存到全局。
  4. 字符串随后被销毁。
  5. 程序从全局取回引用,继续读取。

保存下来的是引用,不是字符串的副本。

当它指向的对象已经不存在,这个引用就成了悬垂引用。AddressSanitizer 在复现中报告了 stack-use-after-scope,即使用了已经离开作用域的栈对象。

这也说明,生命周期标注不会让数据多活一秒。它是一项由编译器验证的承诺。验证出了问题,运行时不会自动替我们续上对象的寿命。

严重,但不等于日常代码随时会踩中

这个漏洞严重,是因为它突破了 safe Rust 应有的安全边界。

不过,漏洞存在多久,与现实中有多容易触发,是两个问题。

维护者判断,要通过它造成内存不安全,需要刻意构造的 Drop 实现,意外触发的可能性较低。目前 issue 的优先级是 P-high。

截至 2026 年 10 月 15 日查阅时,修复 PR #164277 仍在评审。

修复方向是在 MIR 借用检查阶段,补充局部变量类型的合法性检查:不只检查“类型能不能匹配”,还要检查“这个类型是否满足自身的约束”。

我们可以从中学到什么?

对学习 Rust 的人来说,这个 bug 把几个抽象概念串了起来:

生命周期描述借用关系,协变允许安全地缩短使用期限,Drop 则可能在对象销毁时继续操作其中的数据。这些机制必须相互配合,才能守住安全边界。

Rust 的内存安全承诺,需要编译器准确落实。即使应用代码没有 unsafe,编译器本身也仍然可能出错。

而这个漏洞值得关注的地方,就是它把一道平时看不见的边界展示了出来:

一旦编译器接受了某个类型,使用这个类型的代码,就有权相信它声明的约束。

这份信任,是 Rust 安全抽象能够成立的基础。


参考链接




上一篇:芯片后端设计全流程:RTL到GDSII的PPA约束、签核与AI自动化
下一篇:古籍电子版别再到处找了,这个 GitHub 仓库收录 1.8 万部 txt
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-18 07:51 , Processed in 0.065680 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表