Skip to content

Commit

Permalink
Ensure that resume arg outlives region bound for coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Oct 25, 2024
1 parent 45089ec commit ad76564
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 0 deletions.
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

0 comments on commit ad76564

Please sign in to comment.