-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
Open
Labels
A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..)A-pinArea: PinArea: PinC-bugCategory: This is a bug.Category: This is a bug.F-coroutines`#![feature(coroutines)]``#![feature(coroutines)]`F-gen_blocks`gen {}` expressions that produce `Iterator`s`gen {}` expressions that produce `Iterator`sI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-langRelevant to the language teamRelevant to the language teamT-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem teamRelevant to the opsem team
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
Meta
Reproducible on the playground with version 1.96.0-nightly (2026-03-09 2d76d9bc76f27b03b489)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..)A-pinArea: PinArea: PinC-bugCategory: This is a bug.Category: This is a bug.F-coroutines`#![feature(coroutines)]``#![feature(coroutines)]`F-gen_blocks`gen {}` expressions that produce `Iterator`s`gen {}` expressions that produce `Iterator`sI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-langRelevant to the language teamRelevant to the language teamT-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem teamRelevant to the opsem team
Type
Fields
Give feedbackNo fields configured for issues without a type.