这是indexloc提供的服务,不要输入任何密码
Skip to content

Unsoundness due to 'static coroutines that yield non-'static values. #144442

@theemathas

Description

@theemathas

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-coroutinesArea: CoroutinesA-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.F-coroutines`#![feature(coroutines)]`I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-typesRelevant to the types team, which will review and decide on the PR/issue.requires-nightlyThis issue requires a nightly compiler in some way.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions