-
Notifications
You must be signed in to change notification settings - Fork 7
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
Potential use after free using ActorRequest #11
Comments
I agree this should be better documented, thanks for reporting the issue. Marking as unsafe sounds a bit too much, but maybe using #[must_use] to ensure the future is awaited. |
Sorry but I have to strongly disagree. "No matter what, Safe Rust can't cause Undefined Behavior". I'm not an expert, but I think that once you extend the lifetime to I've been trying to implement my own no-alloc actors framework and I really wanted to have a notion of oneshot channels that'd allow me to respond to messages, but I came to the conclusion that it just cannot be done in safe Rust without allocation and I decided to drop this idea. I hope you can prove me wrong ;P |
I'm certainly not an expert either, but I've seen exceptions to this rule in various embedded framework, for instance around DMA, because there might be some edge cases where you could cause undefined behavior, but you'd really have to go out of your way to achieve that. In this case, if you could somewhat enforce that the request() future will await'ed, even if the lifetime of the channel is extended to static, you know that nothing will be referencing it because it cannot be leaked by the receiver (it's private inside the request). So once the notification arrives, you know that the other side will no longer reference the channel (because it's within the actor framework). But last time i looked at must_use, it didn't properly enforce awaiting the future (rust-lang/rust#78149). I thought there was some changes in this recently but haven't found it. |
The issue in the end is async cancellation and there are no real way to prevent it since it can be cancelled from "up", thus even if we require this particular future to be awaited, the function that awaits the request can still be cancelled from where it's called. Ex async cancelled(addr: DynamicAddress<Request<&'static str, &'static str>>) -> &'static str {
// Not cancelled here and awaited as requested by the #[must_use]
addr.request("Hello").await
}
async fn test(addr: DynamicAddress<Request<&'static str, &'static str>>) {
// Cancelled here
let _ = embassy_time::with_timeout(Duration::from_micros(1), cancelled(addr)).await;
} We could instead let the user provide a static In essence we would have pub trait ActorRequest<M, R> {
/// RequestState is created with a 'static channel inside
async fn request(&self, message: M, state: &RequestState) -> R;
} Or we could instead of implementing That would look like: // RequestWrapper is created with a 'static channel inside
impl<'a, M, R> ActorRequest<M, R> for RequestWrapper<'a, Request<M, R>> {
async fn request(&self, message: M) -> R {
self.ignore_previous_requests().await;
let message = Request::new(message, self.reply_addr);
self.notify(message).await;
channel.recv().await
}
} If we do want to fix this, I personally prefer the second solution since we keep the same API and the trait doesn't change. Also I fell it's a bit strange to provide the state in the function argument of the trait, you can clearly see the implementation detail in the trait signature with the first solution. |
@xgroleau Thanks for expanding on this and highlighting the cancellation issue. I prefer the second solution as well for the same reason. |
Here you are illegally transmuting a
DynamicSender<'a, ...>
toDynamicSender<'static, ...>
.ector/ector/src/actor.rs
Lines 77 to 89 in 0505b2f
You are not actually guaranteeing that the channel will outlive the sender. It's possible to call the
request
function withembassy_time::with_timeout
orembassy_futures::select
, which in turn could cancel therecv
's future and drop the channel before the sender is used, resulting in a dangling reference.To illustrate, I edited the examples/request.rs
full source code and miri output here
ActorRequest::request
is a safe API and it should be impossible to cause UB with it, either mark it as unsafe, or rework it. I'm not actually sure you can do this safely without declaring the channel asstatic
or without dynamic allocation.The text was updated successfully, but these errors were encountered: