-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unsoundness due to thread locals #1
Comments
I believe d382efa fixes this? (Not a huge fan of the added verbosity but it's better than allowing UB - would love to hear if there's a better way!) |
Thanks for the super quick response! Adding a lifetime somehow might prevent this kind of escaping. Maybe some GhostCell-style "branded types" can be adapted. The linked commit is not sufficient because the new --- src/main_old.rs 2024-11-19 21:38:23.777954798 +0100
+++ src/main.rs 2024-11-19 21:37:35.257954174 +0100
@@ -7,12 +7,12 @@
};
// async-stream-lite = "=0.1.0"
-use async_stream_lite::{async_stream, YieldFut};
+use async_stream_lite::{async_stream, Yielder};
// futures-core = "0.3.31"
use futures_core::Stream;
thread_local! {
- static YIELD_FUT_USIZE: Cell<Option<YieldFut<usize>>> = const { Cell::new(None) };
+ static YIELD_FUT_USIZE: Cell<Option<Yielder<usize>>> = const { Cell::new(None) };
}
fn main() {
@@ -22,19 +22,18 @@
// Step 1: get our hands on a YieldFut that's ready to write Some(0usize) through the pointer
// stored in `async_stream_lite::STORE`.
- let stream1 = async_stream(|r#yield| async move {
- YIELD_FUT_USIZE.with(|cell| cell.set(Some(r#yield(0usize))));
+ let stream1 = async_stream(|yielder| async move {
+ YIELD_FUT_USIZE.with(|cell| cell.set(Some(yielder)));
});
let _ = pin!(stream1).poll_next(&mut cx);
// Step 2: create a stream that's supposed to yield Box<i32>...
- let stream2 = async_stream(|r#yield| async move {
- drop(r#yield(Box::new(0i32)));
+ let stream2 = async_stream(|_: Yielder<Box<i32>>| async move {
// ... but actually writes Some(0usize) in the `Option<Box<i23>>` destination, using the
// YieldFuture from step 1. Incidentially, this also scribbles over adjacent stack memory
// (`Option<usize>` is twice as big as `Option<Box<i32>>`) so we already have UB here.
- let yield_0_fut: YieldFut<usize> = YIELD_FUT_USIZE.with(|cell| cell.take().unwrap());
- yield_0_fut.await;
+ let yielder: Yielder<usize> = YIELD_FUT_USIZE.with(|cell| cell.take().unwrap());
+ yielder.r#yield(0usize).await;
});
// Step 3: poll the stream and get the bogus `Box` out of it. |
Hi, thanks for publishing this crate! I found it while looking for proc-macro-free
async-stream
alternatives, that would be a great thing to have. Unfortunately the current implementation is unsound, because thread locals can be used to smuggleYieldFuture
s out of the async scope, breaking the stack discipline w.r.t.STATE
that the library tries to enforce. Proof of concept:The text was updated successfully, but these errors were encountered: