Unsoundness due to 'static coroutines that yield non-'static values.
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
This is similar to #112905 and #84366, but it seems different enough so I'm filing a new issue.
The following code causes use-after-free:
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::any::Any;
use std::cell::RefCell;
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
use std::rc::Rc;
type Payload = Box<i32>;
fn make_coro<'a>() -> impl Coroutine<Yield = Rc<RefCell<Option<&'a Payload>>>, Return = ()> + 'static
{
#[coroutine]
|| {
let storage: Rc<RefCell<Option<&'a Payload>>> = Rc::new(RefCell::new(None));
yield storage.clone();
yield storage;
}
}
pub fn expand<'a>(payload: &'a Payload) -> &'static Payload {
let mut coro1 = Box::pin(make_coro::<'a>());
let coro2 = make_coro::<'static>();
let CoroutineState::Yielded(storage) = coro1.as_mut().resume(()) else {
panic!()
};
*storage.borrow_mut() = Some(payload);
extract(coro1, coro2)
}
fn extract<
'a,
F: Coroutine<Yield = Rc<RefCell<Option<&'a Payload>>>, Return = ()> + 'static,
G: Coroutine<Yield = Rc<RefCell<Option<&'static Payload>>>, Return = ()> + 'static,
>(
x: Pin<Box<F>>,
_: G,
) -> &'static Payload {
let mut g: Pin<Box<G>> = *(Box::new(x) as Box<dyn Any>).downcast().unwrap();
let CoroutineState::Yielded(storage) = g.as_mut().resume(()) else {
panic!()
};
storage.borrow().unwrap()
}
fn main() {
let x = Box::new(Box::new(1i32));
let y = expand(&x);
drop(x);
println!("{y}"); // Segfaults
}
The root of the unsoundness here is that the coroutine returned from make_coro mentions the lifetime 'a in the body, so the coroutine should not be 'static. Yet, rust allows it to be 'static anyway. This allows extract to transmute the coroutine from one lifetime to another using Any. The coroutine yields the storage for the payload twice, once before the transmute, and once after. As a result, the storage is treated as two different lifetimes, allowing lifetime extension on arbitrary types.
@rustbot labels +I-unsound +requires-nightly +A-coroutines +F-coroutines +A-lifetimes +T-types
Meta
Reproduces on the playground with version 1.90.0-nightly (2025-07-24 b56aaec52bc0fa35591a)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the minimized coroutine example in the issue and reproduce it on the Rust 1.90.0-nightly playground. Trace the lifetime handling through make_coro, expand, and extract, especially the Any downcast and the two yielded storage values. Done means the example no longer permits a non-'static payload to be returned as 'static or causes use-after-free.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100