about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_type_ir/src/outlives.rs12
-rw-r--r--tests/ui/coroutine/resume-arg-outlives-2.rs34
-rw-r--r--tests/ui/coroutine/resume-arg-outlives-2.stderr17
-rw-r--r--tests/ui/coroutine/resume-arg-outlives.rs27
-rw-r--r--tests/ui/coroutine/resume-arg-outlives.stderr20
5 files changed, 110 insertions, 0 deletions
diff --git a/compiler/rustc_type_ir/src/outlives.rs b/compiler/rustc_type_ir/src/outlives.rs
index ac35215fbaa..0e94e989b97 100644
--- a/compiler/rustc_type_ir/src/outlives.rs
+++ b/compiler/rustc_type_ir/src/outlives.rs
@@ -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
             }
diff --git a/tests/ui/coroutine/resume-arg-outlives-2.rs b/tests/ui/coroutine/resume-arg-outlives-2.rs
new file mode 100644
index 00000000000..387b143ea27
--- /dev/null
+++ b/tests/ui/coroutine/resume-arg-outlives-2.rs
@@ -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();
+}
diff --git a/tests/ui/coroutine/resume-arg-outlives-2.stderr b/tests/ui/coroutine/resume-arg-outlives-2.stderr
new file mode 100644
index 00000000000..3d630d7e7e4
--- /dev/null
+++ b/tests/ui/coroutine/resume-arg-outlives-2.stderr
@@ -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`.
diff --git a/tests/ui/coroutine/resume-arg-outlives.rs b/tests/ui/coroutine/resume-arg-outlives.rs
new file mode 100644
index 00000000000..258be28e063
--- /dev/null
+++ b/tests/ui/coroutine/resume-arg-outlives.rs
@@ -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("");
+}
diff --git a/tests/ui/coroutine/resume-arg-outlives.stderr b/tests/ui/coroutine/resume-arg-outlives.stderr
new file mode 100644
index 00000000000..2a6337b4945
--- /dev/null
+++ b/tests/ui/coroutine/resume-arg-outlives.stderr
@@ -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
+