Hacker News Best

11 year old unsoundness bugs found in Rust borrow checker

6.9内容质量

TL;DR · AI 摘要

struct's lifetime requirements can be bypassed via covariant field assignment · Issue 164250 · rust-lang/rust · GitHub R...

核心要点

  • 主题聚焦:11 year old unsoundness bugs found in Rust borro
  • 来源:Hacker News Best,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#前端#后端#开源
打开原文

struct's lifetime requirements can be bypassed via covariant field assignment · Issue #164250 · rust-lang/rust · GitHub

Repository navigation

$

struct's lifetime requirements can be bypassed via covariant field assignment #164250

$!

New issue

/$

Copy link

Open

struct's lifetime requirements can be bypassed via covariant field assignment

#164250

Labels

A-borrow-checker

Area: The borrow checker

A-destructors

Area: Destructors (Drop, …)

A-lifetimes

Area: Lifetimes / regions

A-variance

Area: Variance (https://doc.rust-lang.org/nomicon/subtyping.html)

C-bug

Category: This is a bug.

I-unsound

Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness

P-high

High priority

T-types

Relevant to the types team, which will review and decide on the PR/issue.

Description

jsnell

opened

on Oct 13, 2026

Issue body actions

Note

Given that this issue has been shared online and is currently getting quite a few visitors, here's some additional context.

Rust's type system is intended to prevent memory unsafety. Upholding this promise is something we take very seriously, which is why we've been working on things like the next-generation trait solver , the region constraint rework , and more generally Project Zero . It is a large priority of the Types Team to fix all type system unsoundnesses and to work towards there being a solid foundation to minimize the risk of unknown ones.

This bug is a P-high issue and would have been P-critical regardless of whether people encountered it in the wild. The reason that it is "only P-high " is that it's 11 years old. A bug like this which has not been found during the last 11 years cannot actually be that easy to hit.

Looking at the bug itself, it is also very likely that this unsoundness has not caused memory unsafety by accident. While it looks easy to trigger, to exploit this you need an intentionally malicious Drop implementation.

~ @lcnr

In the following code:

code
use
std
::
sync
::
Mutex
;
static
KEPT
:
Mutex
<
Vec
<
Box
<
dyn
std
::
any
::
Any
+
Send
>
>
>
=
Mutex
::
new
(
Vec
::
new
(
)
)
;
struct
Q
<
T
:
'
static
+
Send
>
(
Option
<
T
>
)
;
impl
<
T
:
'
static
+
Send
>
Drop
for
Q
<
T
>
{
fn
drop
(
&
mut
self
)
{
if
let
Some
(
t
)
=
self
.
0
.
take
(
)
{
KEPT
.
lock
(
)
.
unwrap
(
)
.
push
(
Box
::
new
(
t
)
)
;
}
}
}
fn
main
(
)
{
{
let
l =
String
::
from
(
"hello world, this is long enough"
)
;
let
mut
q =
Q
(
None
)
;
q
.
0
=
Some
(
&
l
)
;
}
let
_z =
String
::
from
(
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
)
;
let
k =
KEPT
.
lock
(
)
.
unwrap
(
)
;
println
!
(
"{}"
,
k
[
0
]
.
downcast_ref
::
<
&
'
static
String
>
(
)
.
unwrap
(
)
)
;
}

I expected to see this happen: program is not accepted

Instead, this happened: program compiles and prints XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX . With asan, the program will crash with a AddressSanitizer: stack-use-after-scope

Meta

Issue on current nightly, and stables at least down to 1.85.0

Godbolt with asan: https://rust.godbolt.org/z/83MvfbKde Godbolt without asan: https://rust.godbolt.org/z/GsGdqqMWe

Reactions are currently unavailable

Activity

Next

  • jsnell added C-bug Category: This is a bug. Category: This is a bug. on Oct 13, 2026
  • rustbot added needs-triage This issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging This issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging on Oct 13, 2026
  • theemathas added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness on Oct 13, 2026
  • rustbot added I-prioritize Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical} Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical} on Oct 13, 2026
  • theemathas added A-borrow-checker Area: The borrow checker Area: The borrow checker T-types Relevant to the types team, which will review and decide on the PR/issue. Relevant to the types team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging This issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging on Oct 13, 2026
  • theemathas commented on Oct 13, 2026 theemathas on Oct 13, 2026 Last edited by theemathas Contributor More actions The core of the unsoundness: struct Q < T : ' static > ( Option < T > ) ; impl < T : ' static > Drop for Q < T > { fn drop ( & mut self ) { // Code in here has access to a non-'static &String // but assumes that it's a &'static String } } fn main ( ) { let l = String :: new ( ) ; let mut q = Q ( None ) ; q . 0 = Some ( & l ) ; } 👀 React with 👀 15 Reacted by Daniel Vogelbacher, Serhiy, BioTomateDE, Timur Tugushev, palapapa, Ori Yadid, SeniorMars, dzmitry-lahoda, Til, Marco Quinten and 5 more
  • theemathas added A-destructors Area: Destructors (Drop, …) Area: Destructors (Drop, …) on Oct 13, 2026
  • rustbot removed I-prioritize Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical} Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical} on Oct 13, 2026
  • rustbot commented on Oct 13, 2026 rustbot on Oct 13, 2026 · Hidden as outdated show comment More actions
  • rustbot added P-critical Critical priority Critical priority on Oct 13, 2026
  • theemathas commented on Oct 13, 2026 theemathas on Oct 13, 2026 Last edited by theemathas Contributor More actions My version of the code has been compiling ever since 1.18.0 (2017) How on earth was this not found before this??? 👀 React with 👀 20 Reacted by Max, Alisa Sireneva, Leon, maxbry-uh, Luke, slaiyer, Screeching Bagel, Slanterns, maliciousblahaj, Timur Tugushev and 10 more
  • theemathas commented on Oct 13, 2026 theemathas on Oct 13, 2026 Contributor More actions Possibly related: #128225
  • lcnr commented on Oct 13, 2026 lcnr on Oct 13, 2026 Contributor More actions without relying on a static 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}" ) ; } 🚀 React with 🚀 3 Reacted by dzmitry-lahoda, Martijn Gribnau and Dale Weiler
  • 4 remaining items Load more
  • theemathas commented on Oct 13, 2026 theemathas on Oct 13, 2026 · Hidden as duplicate show comment More actions
  • maxdexh commented on Oct 13, 2026 maxdexh on Oct 13, 2026 Last edited by maxdexh Member More actions Another version that segfaults/prints freed memory since 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 changed the title [-] struct's T: 'static bound bypassed via covariant field assignment [/-] [+] struct's lifetime requirements can be bypassed via covariant field assignment [/+] on Oct 13, 2026
  • maxdexh added A-lifetimes Area: Lifetimes / regions Area: Lifetimes / regions A-variance Area: Variance (https://doc.rust-lang.org/nomicon/subtyping.html) Area: Variance (https://doc.rust-lang.org/nomicon/subtyping.html) on Oct 13, 2026
  • theemathas commented on Oct 13, 2026 theemathas on Oct 13, 2026 Last edited by theemathas Contributor More actions Another version, cuz why not. I think this version is probably the easiest to understand. 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 ) { // Code in here has access to a non-'static &String // but assumes that it's a &'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 is supposed to be a &'static String, but we're assigning it a non-'static &String } thread :: sleep ( Duration :: from_secs ( 2 ) ) ; } 👍 React with 👍 2 Reacted by Marek Šimek and Christopher Knight 👀 React with 👀 1 Reacted by Filip Dobrosavljević
  • maxdexh commented on Oct 13, 2026 maxdexh on Oct 13, 2026 Last edited by maxdexh Member More actions I think unification just generally doesn't check lifetime bounds of the type. Example with unifying coercion, without field assignment: struct Q < T : Copy , F : FnMut ( T ) > ( T , F ) ; impl < T : Copy , F : FnMut ( T ) > Drop for Q < T , F > { fn drop ( & mut self ) { self . 1 ( self . 0 ) } } type T = Vec < Vec < i32 > > ; fn main ( ) { let long = Box :: leak ( Box :: new ( T :: new ( ) ) ) as & ' static T ; { let local : T = vec ! [ vec! [ 1 ] ] ; // item type is Q<&'local T, fn(&'static T)>, which is ill-formed let mut unified = [ Q ( long , ( | s : & ' static T | { std :: thread :: spawn ( move || { println ! ( "{s:?}" ) ; } ) ; } ) as fn ( & ' static T ) , ) , Q ( & local , ( |_ : & T | { } ) as fn ( & T ) ) , ] ; let [ l , r ] = & mut unified ; std :: mem :: swap ( & mut l . 0 , & mut r . 0 ) ; } std :: thread :: sleep_ms ( 1000 ) ; } 👀 React with 👀 5 Reacted by 9291Sam, Bee4Queer, Maple, Thefool and T-256
  • srtdog64 added a commit that references this issue on Oct 13, 2026 fix: refuse handles in aggregate storage on both front ends ... da227fc
  • maxdexh added regression-from-stable-to-stable Performance or correctness regression from one stable version to another. Performance or correctness regression from one stable version to another. on Oct 13, 2026
  • mueddib commented on Oct 13, 2026 mueddib on Oct 13, 2026 · Hidden as low-quality show comment More actions
  • apiraino commented on Oct 13, 2026 apiraino on Oct 13, 2026 Contributor More actions Hello everyone, mod here. We're preventively locking this issue to prevent spam and other unhelpful comments
  • rust-lang locked as too heated and limited conversation to collaborators on Oct 13, 2026
  • rust-lang unlocked this conversation on Oct 13, 2026
  • rust-lang locked and limited conversation to collaborators on Oct 13, 2026
  • lcnr removed regression-from-stable-to-stable Performance or correctness regression from one stable version to another. Performance or correctness regression from one stable version to another. on Oct 13, 2026

Sign up for free

to join this conversation on GitHub.

Already have an account?

Sign in to comment

Metadata

Assignees

No one assigned

Labels

Type

No type

Projects

  • T-types unsound issues Status open/unblocked Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

  • Open in GitHub Copilot app