-
Couldn't load subscription status.
- Fork 9
Description
Sorry to be the bearer of bad news. Pinning is a tricky subject and can be quite subtle.
Describe the bug
The current implementation of select allows for moving a future which is assumed to be pinned. Among other potential issues, this enables reading freed memory in safe code.
To Reproduce
The following should showcase the issue:
Edit: More compact example, and a bit more comments.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::mem;
use futures::future;
use pasts::prelude::*;
async fn doit(leak: u128) {
if leak == 42 {
// Take a reference to leak which is a local variable to this function. And have it live across an await.
let leak = &leak;
println!("{} {:?}", leak, leak as *const _);
let mut pending = true;
// future that only returns pending the first time it's polled.
future::poll_fn(move |_| if mem::take(&mut pending) {
Poll::Pending
} else {
Poll::Ready(())
}).await;
println!("{} {:?}", leak, leak as *const _);
} else {
// do nothing to drive the select
}
}
#[tokio::main]
async fn main() {
let mut v = [doit(42), doit(0)];
println!("old location of future: {:?}", v.as_ptr());
v.select().await;
// move the future.
let a = std::mem::replace(&mut v[0], doit(0));
println!("new location of future: {:?}", &a as *const _);
a.await;
}Running it in debug mode for me gives:
old location of future: 0x7ffe11d595b0
42 0x7ffe11d595c0
new location of future: 0x7ffe11d59618
2595995492391351414651693012353024 0x7ffe11d595c0In effect: The second read of the reference to &foo uses an outdated memory location, since the future has been moved the second time it was polled.
Expected behavior
The select implementation should require the futures to be Unpin, or maintain Pin invariants in some other way to prevent this from compiling.