Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure that resume arg outlives region bound for coroutines #132151

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions compiler/rustc_type_ir/src/outlives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ impl<I: Interner> TypeVisitor<I> for OutlivesCollector<'_, I> {
ty::Coroutine(_, args) => {
args.as_coroutine().tupled_upvars_ty().visit_with(self);

// Coroutines may not outlive a region unless the resume
// ty outlives a region. This is because the resume ty may
// store data that lives shorter than this outlives region
// across yield points, which may subsequently be accessed
// after the coroutine is resumed again.
//
// Conceptually, you may think of the resume arg as an upvar
// of `&mut Option<ResumeArgTy>`, since it is kinda like
// storage shared between the callee of the coroutine and the
// coroutine body.
args.as_coroutine().resume_ty().visit_with(self);

// We ignore regions in the coroutine interior as we don't
// want these to affect region inference
}
Expand Down
34 changes: 34 additions & 0 deletions tests/ui/coroutine/resume-arg-outlives-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Regression test for 132104

#![feature(coroutine_trait, coroutines)]

use std::ops::Coroutine;
use std::{thread, time};

fn demo<'not_static>(s: &'not_static str) -> thread::JoinHandle<()> {
let mut generator = Box::pin({
#[coroutine]
move |_ctx| {
let ctx: &'not_static str = yield;
yield;
dbg!(ctx);
}
});

// exploit:
generator.as_mut().resume("");
generator.as_mut().resume(s); // <- generator hoards it as `let ctx`.
//~^ ERROR borrowed data escapes outside of function
thread::spawn(move || {
thread::sleep(time::Duration::from_millis(200));
generator.as_mut().resume(""); // <- resumes from the last `yield`, running `dbg!(ctx)`.
})
}

fn main() {
let local = String::from("...");
let thread = demo(&local);
drop(local);
let _unrelated = String::from("UAF");
thread.join().unwrap();
}
17 changes: 17 additions & 0 deletions tests/ui/coroutine/resume-arg-outlives-2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0521]: borrowed data escapes outside of function
--> $DIR/resume-arg-outlives-2.rs:20:5
|
LL | fn demo<'not_static>(s: &'not_static str) -> thread::JoinHandle<()> {
| ----------- - `s` is a reference that is only valid in the function body
| |
| lifetime `'not_static` defined here
...
LL | generator.as_mut().resume(s); // <- generator hoards it as `let ctx`.
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `s` escapes the function body here
| argument requires that `'not_static` must outlive `'static`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0521`.
27 changes: 27 additions & 0 deletions tests/ui/coroutine/resume-arg-outlives.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Regression test for 132104

#![feature(coroutine_trait, coroutines)]

use std::ops::Coroutine;
use std::pin::Pin;

fn demo<'not_static>(s: &'not_static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'static>> {
let mut generator = Box::pin({
#[coroutine]
move |ctx: &'not_static str| {
yield;
dbg!(ctx);
}
});
generator.as_mut().resume(s);
generator
//~^ ERROR lifetime may not live long enough
}

fn main() {
let local = String::from("...");
let mut coro = demo(&local);
drop(local);
let _unrelated = String::from("UAF");
coro.as_mut().resume("");
}
20 changes: 20 additions & 0 deletions tests/ui/coroutine/resume-arg-outlives.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: lifetime may not live long enough
--> $DIR/resume-arg-outlives.rs:17:5
|
LL | fn demo<'not_static>(s: &'not_static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'static>> {
| ----------- lifetime `'not_static` defined here
...
LL | generator
| ^^^^^^^^^ returning this value requires that `'not_static` must outlive `'static`
|
help: consider changing `impl Coroutine<&'not_static str> + 'static`'s explicit `'static` bound to the lifetime of argument `s`
|
LL | fn demo<'not_static>(s: &'not_static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'not_static>> {
| ~~~~~~~~~~~
help: alternatively, add an explicit `'static` bound to this reference
|
LL | fn demo<'not_static>(s: &'static str) -> Pin<Box<impl Coroutine<&'not_static str> + 'static>> {
| ~~~~~~~~~~~~

error: aborting due to 1 previous error

Loading