Soundness hole: Use-after-free through mismatching lifetimes of stream item and yielded type
This is almost trivially simple to run into. Just yield something that's shorter-lived than what the place using the stream requires, and … it just compiles.
(The only “trick” to avoiding compiler errors with this is that one needs to have the stream itself must not be kept alive for too long; i.e. significantly shorter than the items one gets from it.)
use async_stream::stream;
use futures::StreamExt;
use std::pin::pin;
#[tokio::main]
async fn main() {
let mut outer = vec![];
{
let v = vec![0; 10];
let v_ref = &v;
let mut s = pin!(stream! {
for x in v_ref {
yield x
}
});
while let Some(x) = s.next().await {
outer.push(x);
}
};
// use-after-free
println!("{outer:?}"); // […garbage allocator internals…, 0, 0, 0]
}
The relevant problem here is likely the covariance of the Sender type; I'm not 100% sure I understand what part of the expanded code currently allows for the corresponding unsound coercion to happen, nor do I know whether this was always accepted by the compiler, but even before that, the wrong covariance would've been not future-proof.
In my experience, this Sender/Receiver helper type pair that's supposed to ensure the yielded type is matching the item type might as well be unified into a single type, anyway. Especially after #106 is fixed, there's not really any point in involving a mutable borrow into this, it could simply be a simple Copy marker type that serves both roles of the current __yield_tx and __yield_rx (and .send method gets a by-value fn …(self, …) argument). The PhantomData inside would need to be invariant then, e.g. via PhantomData<fn(T) -> T>.
A minimal change on the other hand would be to just make the Sender<T> invariant or contravariant in <T>. With such a fix, we can gain the desired compilation error:
error[E0597]: `v` does not live long enough
--> src/main.rs:11:21
|
10 | let v = vec![0; 10];
| - binding `v` declared here
11 | let v_ref = &v;
| ^^ borrowed value does not live long enough
...
20 | };
| - `v` dropped here while still borrowed
21 | println!("{outer:?}");
| --------- borrow later used here
Soundness hole: Use-after-free through mismatching lifetimes of stream item and yielded type
This is almost trivially simple to run into. Just yield something that's shorter-lived than what the place using the stream requires, and … it just compiles.
(The only “trick” to avoiding compiler errors with this is that one needs to have the stream itself must not be kept alive for too long; i.e. significantly shorter than the items one gets from it.)
(run on rustexplorer.com)
The relevant problem here is likely the covariance of the
Sender
type; I'm not 100% sure I understand what part of the expanded code currently allows for the corresponding unsound coercion to happen, nor do I know whether this was always accepted by the compiler, but even before that, the wrong covariance would've been not future-proof.In my experience, this
Sender
/Receiver
helper type pair that's supposed to ensure theyield
ed type is matching the item type might as well be unified into a single type, anyway. Especially after #106 is fixed, there's not really any point in involving a mutable borrow into this, it could simply be a simpleCopy
marker type that serves both roles of the current__yield_tx
and__yield_rx
(and.send
method gets a by-valuefn …(self, …)
argument). ThePhantomData
inside would need to be invariant then, e.g. viaPhantomData<fn(T) -> T>
.A minimal change on the other hand would be to just make the
Sender<T>
invariant or contravariant in<T>
. With such a fix, we can gain the desired compilation error: