ESC
开源 3 分钟阅读

Rust 借用检查器被发现存在长达 11 年的健全性漏洞

Rust 编译器被曝多个健全性漏洞:利用 Drop 实现中的协变字段赋值可绕过借用检查器的生命周期约束,造成 use-after-scope 等未定义行为。相关代码自 Rust 1.4/1.18(2015-2017 年)即可编译通过,影响当前 nightly 及低至 1.85.0 的 stable 版本,已被官方标记为 P-critical 优先级。

来源:Hacker News

然而实际发生的是:程序编译通过并打印 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。如果启用 asan,程序会崩溃并报错 AddressSanitizer: stack-use-after-scope。

该问题在当前 nightly 版本以及至少低至 1.85.0 的 stable 版本上均可复现。

启用 asan 的 Godbolt 在线演示:https://rust.godbolt.org/z/83MvfbKde

未启用 asan 的 Godbolt 在线演示:https://rust.godbolt.org/z/GsGdqqMWe

表情回应功能目前不可用

动态

下一页

  • jsnell 添加了 C-bug 标签(类别:这是一个 bug)(2026 年 10 月 13 日)
  • rustbot 添加了 needs-triage 标签(该 issue 可能需要分诊,处理完成后移除。详见 forge.rust-lang.org/release/issue-triaging)(2026 年 10 月 13 日)
  • theemathas 添加了 I-unsound 标签(问题:健全性漏洞,属于最严重的 bug 类型,参见:https://en.wikipedia.org/wiki/Soundness)(2026 年 10 月 13 日)
  • rustbot 添加了 I-prioritize 标签(该 issue 需要团队成员评估影响,之后将被 P-{low,medium,high,critical} 取代)(2026 年 10 月 13 日)
  • theemathas 添加了 A-borrow-checker 标签(领域:借用检查器)和 T-types 标签(与类型团队相关,由其审查并决定该 PR/issue 的处理),并移除了 needs-triage 标签(2026 年 10 月 13 日)

theemathas 于 2026 年 10 月 13 日评论

(由贡献者 theemathas 最后编辑)

健全性漏洞的核心:

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

impl<T: 'static> Drop for Q<T> {
    fn drop(&mut self) {
        // 这里的代码可以访问一个非 'static 的 &String
        // 但却假定它是一个 &'static String
    }
}

fn main() {
    let l = String::new();
    let mut q = Q(None);
    q.0 = Some(&l);
}

👀 已有 15 人以此回应:Daniel Vogelbacher、Serhiy、BioTomateDE、Timur Tugushev、palapapa、Ori Yadid、SeniorMars、dzmitry-lahoda、Til、Marco Quinten 等 15 人

theemathas 于 2026 年 10 月 13 日评论

(由贡献者 theemathas 最后编辑)

我这个版本的代码从 1.18.0(2017 年)起就能编译通过。

在此之前怎么可能一直没被发现???

struct MyType<'a, 'b: 'a, 'c>(&'c mut &'a str, &'b str);

impl<'a, 'b: 'a, 'c> Drop for MyType<'a, 'b, 'c> {
    fn drop(&mut self) {
        *self.0 = self.1;
    }
}

fn main() {
    let mut me_out = "rarw";
    {
        let l = String::from("some cool string");
        let mut q = MyType(&mut me_out, "");
        q.1 = &l;
        
    }
    println!("me_out: {me_out}");
}

🚀 已有 3 人以此回应:dzmitry-lahoda、Martijn Gribnau、Dale Weiler

  • 另有 4 条动态
  • 加载更多
  • theemathas 于 2026 年 10 月 13 日的另一条评论(因过时被隐藏,展开评论)

maxdexh 于 2026 年 10 月 13 日评论

(由成员 maxdexh 最后编辑)

另一个自 1.4 起就会段错误/打印已释放内存的版本:

struct Q<T, F: FnMut(&T)>(Option<T>, F);

impl<T, F: FnMut(&T)> Drop for Q<T, F> {
    fn drop(&mut self) {
        self.1(self.0.as_ref().unwrap())
    }
}

fn main() {
    let mut out: Option<&'static _> = None;
    {
        let l = vec![vec![1]];
        let mut q = Q(None, |s| {
            out = Some(*s);
        });
        q.0 = Some(&l);
    }
    println!("{:?}", out);
}
  • lcnr 修改了标题:从「struct’s T: 'static bound bypassed via covariant field assignment」改为「struct’s lifetime requirements can be bypassed via covariant field assignment」(2026 年 10 月 13 日)
  • maxdexh 添加了 A-lifetimes 标签(领域:生命周期 / regions)和 A-variance 标签(领域:型变,参见 https://doc.rust-lang.org/nomicon/subtyping.html)(2026 年 10 月 13 日)

theemathas 于 2026 年 10 月 13 日评论

(由贡献者 theemathas 最后编辑)

再来一个版本。我觉得这个版本可能是最容易理解的。

use std::time::Duration;
use std::thread;

struct Q<'a: 'static>(Option<&'a String>);

impl<'a: 'static> Drop for Q<'a> {
    fn drop(&mut self) {
        // 这里的代码可以访问一个非 'static 的 &String
        // 但却假定它是一个 &'static String
        let wrong: &'static String = self.0.unwrap();
        thread::spawn(move || {
            thread::sleep(Duration::from_secs(1));
            println!("{}", wrong);
        });
    }
}

fn main() {
    {
        let l = String::from("abc");
        let mut q = Q(None);
        q.0 = Some(&l); // q.0 本应是 &'static String,但我们赋给它的是一个非 'static 的 &String
    }
    thread::sleep(Duration::from_secs(2));
}

👍 已有 2 人以此回应:Marek Šimek、Christopher Knight

👀 已有 1 人以此回应:Filip Dobrosavljević

maxdexh 于 2026 年 10 月 13 日评论

(由成员 maxdexh 最后编辑)

我认为统一(unification)过程通常根本不会检查类型的生命周期约束。下面是一个通过统一时强制转换触发问题的例子(不涉及字段赋值):