`pin!()` (and any other unsafe code relying on local variables having a fixed location) in a coroutine or `gen` block is unsound
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Coroutines whose local variables don't borrow each other across a yield point implements Unpin. Moving an ongoing Unpin coroutine causes all its alive local variables to be moved. pin!() relies on the assumption that an unnamable local variable cannot be moved. All of this combined together results in an unsoundness.
The below code causes a use-after-free. In my testing, it prints 8192, which is presumably some garbage data lying around.
#![feature(coroutines, coroutine_trait)]
use std::ops::Coroutine;
use std::pin::{Pin, pin};
use std::task::{Context, Waker};
// Pins the value, calls the callback with it, moves the value, then drops it.
// This violates the Pin guarantee
fn wrong_pin<T>(value: T, callback: impl FnOnce(Pin<&mut T>)) {
let mut coro = Box::new(Some(
#[coroutine]
|| {
let value = value;
// Move the value into an unnamable variable, and lifetime-extend it
let pinned_value = pin!(value);
callback(pinned_value);
// The unnamable variable is still alive by the time we yield
yield;
},
));
// Run the coroutine until the yield
let _ = Pin::new((&mut *coro).as_mut().unwrap()).resume(());
// Move the coroutine while the unnamable variable is still alive
let moved = *coro;
// Overwrite the heap memory to make the UB easily observable.
*coro = None;
drop(coro);
// Drop the unnamable variable
drop(moved);
}
// Cause UB with the Pin guarantee violation
fn main() {
let fut = async {
// Work around #63818, to make it clear that this is a distinct issue
let dummy = std::marker::PhantomPinned;
let x = 1;
// Make the future self-referential,
// and access that reference when the future is dropped
let _y = PrintOnDrop(&x);
std::future::pending::<()>().await;
// Hold the value across an await point, so the generated future
// contains the value and becomes !Unpin, maybe? Not sure.
let _dummy = dummy;
};
wrong_pin(fut, |pinned_fut| {
// Run the future until the await point
let mut ctx = Context::from_waker(Waker::noop());
let _ = pinned_fut.poll(&mut ctx);
});
}
struct PrintOnDrop<'a>(&'a i32);
impl Drop for PrintOnDrop<'_> {
fn drop(&mut self) {
println!("{}", self.0);
}
}
Miri output:
error: Undefined Behavior: constructing invalid value at .<enum-variant(Some)>.0.<coroutine-state(3)>.<captured-var(value)>.<coroutine-state(3)>.<captured-var(2)>.0: encountered a dangling reference (use-after-free)
--> src/main.rs:29:10
|
29 | drop(moved);
| ^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: stack backtrace:
0: wrong_pin::<{async block@src/main.rs:34:15: 34:20}, {closure@src/main.rs:46:20: 46:32}>
at src/main.rs:29:10: 29:15
1: main
at src/main.rs:46:5: 50:7
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
cc @RalfJung, @dianne
Meta
Reproducible on the playground with version 1.96.0-nightly (2026-03-09 2d76d9bc76f27b03b489)
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 reproducer in src/main.rs and run it under Miri, using the reported nightly version and backtrace. Read the pin!, coroutine, gen, and Unpin behavior involved; the issue is resolved when the demonstrated use-after-free is no longer possible and the Miri run no longer reports undefined behavior.
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
- Needs clarification
- Newbie friendliness
- 25/100