Rust 借用檢查器 11 年舊洞:賦值 covariant 欄位可繞過 T: 'static
rust-lang/rust issue #164250 · 2026-10-13 開立
Rust 的借用檢查器讓 struct 上宣告的 lifetime 約束可以被「賦值給 covariant 欄位」繞過:struct Q<T: 'static> 明明要求 T: 'static,卻能在 Drop 裡拿到一個非 'static 的引用,並當成 &'static 使用。整個案例不含任何 unsafe。
這個問題由 jsnell 在 2026-10-13 回報為 #164250,標籤含 I-unsound、T-types。Types Team 的 lcnr 在 issue 中說這個 bug 已存在 11 年;目前 issue 與修補 PR #164277 皆未關閉。
漏洞機制
原始回報用一個 static 的 Mutex 加上 struct Q<T: 'static + Send>(Option<T>)。Drop 實作把內容搬進那個 static。在 main 的內層作用域中,把一個 String 的引用賦值到 q.0,之後從 static 讀出 &'static String。作者預期編譯器拒絕,實際上編譯通過,執行時印出亂碼,AddressSanitizer 回報 stack-use-after-scope。
theemathas 抽出最小核心:
struct Q<T: 'static>(Option<T>);
impl<T: 'static> Drop for Q<T> {
fn drop(&mut self) {
// 這裡拿到的 &String 並非 'static,
// 但程式碼假設它是 'static
}
}問題的根在於:賦值欄位時,欄位的型別透過 covariance 被縮短 lifetime,而型別是否仍滿足 struct 宣告的 bound 沒有被重新檢查。maxdexh 在討論串中的說法是,unification 並不會檢查型別的 lifetime bound;他的範例用 Q<T: Copy, F: FnMut(T)> 並對調陣列欄位,指出該項目型別本身就是 ill-formed。
受影響版本
- 回報者確認:目前 nightly,以及至少回溯到
1.85.0的 stable。 - theemathas 指出,最小案例自
1.18.0(2017 年)起就能編譯。 - maxdexh 的變體(
Q<T, F: FnMut(&T)>,會印出已釋放的記憶體)據其所述自1.4起就存在。 - lcnr 另給出不含
static的例子,用MyType<'a, 'b: 'a, 'c>,Drop內寫*self.0 = self.1,最後使用懸空引用。
theemathas 還給出一個在 Drop 中呼叫 thread::spawn、sleep 後印出懸空 &'static String 的版本,說明引用可以逃出原本的作用域。
修補與目前狀態
lcnr 的說明是:Types Team 把修型別系統的 unsoundness 視為高優先,理由包括 next-generation trait solver、region constraint 的重做與 Project Zero;同時,這個洞存在 11 年,不太容易碰到,要利用似乎需要刻意寫出惡意的 Drop。標籤上 rustbot 一度加上 P-critical,目前為 P-high;regression-from-stable-to-stable 已被 lcnr 移除。討論串曾被鎖定、解鎖後再度鎖定,以阻擋無助益的留言。
修補 PR #164277「require MIR local types to be well-formed」由 Li-Zheng-Rong 在同日開立,僅改 compiler/rustc_borrowck/src/type_check/mod.rs。做法是對 LocalKind::Temp 的 local,在 borrowck 中額外證明其型別的 WellFormed clause:
if self.body.local_kind(local) == LocalKind::Temp {
self.prove_clause(
ty::ClauseKind::WellFormed(local_decl.ty.into()),PR 目前未合併,尚無核准;dingxiangfei2009 在審查中問是否也該對 cast 與 user type ascription 做 WF 檢查,頁面上尚無回覆。PR 頁面未提供 crater 或效能數據。
這也解釋了為何此洞能潛伏這麼久:每一個單獨的步驟都合法。賦值欄位本身合法,covariance 縮短 lifetime 合法,Drop 內使用 T: 'static 的假設也合法;錯誤只出現在三者組合之後,型別已不再滿足 struct 的 bound,卻沒有任何一個檢查點負責發現。theemathas 在串中也直言驚訝:「How on earth was this not found before this???」
影響範圍
對一般應用程式與函式庫作者,目前沒有需要改的程式碼:這不是正常寫法會觸發的情況,而且修補尚未合併。真正需要留意的是在 Drop 內依賴 T: 'static 做 unsafe 或全域狀態操作的 crate,以及把「safe Rust 不會 use-after-free」當作安全邊界的沙箱、插件載入與不信任程式碼編譯服務:這些場景下,惡意程式碼可以自行構造觸發條件。
另一個值得記下的連結是 #128225。theemathas 認為可能相關:該 issue 是 2024-07-26 開立的另一個「整體賦值可通過、欄位賦值卻報 E0597」的不一致;討論串中 RalfJung 提出的解釋是 strong(可改變型別)賦值。兩者是否同源,issue 中未下定論。