夕阳下的旷野中一截被砍断的树桩特写,树干已经不在了,只剩残根留在原地

一段 20 行的程序,本该编译不过

2026 年 10 月 13 日,开发者在 rust-lang/rust 仓库开了 issue #164250,标题是「结构体的生命周期要求可以被协变字段赋值绕过」。最小复现很短:定义一个 Q<T: 'static + Send>(Option<T>),并为它实现 Drop——析构时把里面的值取出、装箱塞进一个全局的 Mutex<Vec<Box<dyn Any + Send>>>。然后在 main 里创建一个局部字符串 l,先构造 Q(None),再执行 q.0 = Some(&l)。

按类型系统的承诺,这一步应该被拒绝:T 被要求是 'static,而 &l 的生存期只到当前作用域。但它编译通过了。等 l 所在的作用域结束、l 已被销毁,程序再把全局里存着的东西取出来,downcast 回 &'static String 并打印,输出的是那块已经失效的内存里的内容。开 AddressSanitizer 跑,会直接报 stack-use-after-scope。

问题出在方差与析构函数的组合

这个 issue 被打上 I-unsound、A-borrow-checker、A-variance、A-destructors、A-lifetimes、T-types 等标签,说明它同时踩到了方差(协变)、析构与生命周期检查三块。协变本身意味着字段类型允许把长生存期的引用当短生存期来用;方向被反着用之后,短生存期的引用就有了挤进「必须 'static」位置的机会,再叠加自定义 Drop 把值搬出自身,引用就活过了它指向的那个变量。issue 里写明:当前 nightly 可复现,stable 至少回溯到 1.85.0,作者还附了带 asan 与不带 asan 的两个 Godbolt 链接。

官方定级 P-high 的理由,很坦率

维护者 lcnr 在 issue 里补了一段背景说明,态度比结论更有意思。他确认这是一个 11 年的老漏洞,并说明类型团队的目标是修掉所有类型系统层面的 unsound,正在推进下一代 trait solver、region constraint 重做以及 Project Zero。至于为什么只定 P-high 而不是 P-critical:它在 11 年里没被人在真实场景里撞到,说明实际上没那么容易触发;而且看漏洞的形态,它大概率没有「不小心」造成内存不安全——想利用它,你得故意写一个行为恶意的 Drop。也就是说,这不是「有人靠它越过了安全边界」,而是「安全保证的边界上有一个洞,需要一个有心人加一段反常代码才能钻过去」。

顺带值得记一句:Rust 一直把「编译器替你挡住内存安全问题」当作核心卖点。这类 issue 的价值,是把这句话从「永真」还原成「一个有人在持续修补、并且公开接受被证伪的工程系统」。

对做企业数字化、小团队的实际启发

三条。第一,「不可能出错」的技术保证,要按「已知漏洞 + 修复优先级」来管理:给客户做选型时提到 Rust、提到类型安全与内存安全,加上一句「语言层面的缺陷公开可查、有定级和修复流程」,比说「不会出这类问题」更专业也更站得住。第二,这个漏洞的触发条件给出一个通用提醒:自定义析构、把对象里的值搬走再交给全局持有,是生命周期分析最容易翻车的地方——不只 Rust,任何带手工资源释放或对象池的系统里,这种写法都该单独评审。第三,看维护者的处理方式:公开承认、定级、讲清利用门槛、把修法排进长期路线。给客户的系统出问题时,这套动作比一句「我们已经修复了」更能保住信任。