Potential double free issue
`Task::run` has got the invariant that it must not be called if the state for the task is `COMPLETED`. However, that might get violated assuming this scenario- Assume we have a future that when polled clones the waker and stores it in some shared state that can be accessed by another thread like-
```rust
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
struct CounterFuture {
count: usize,
waker: Arc<Mutex<Option<Waker>>>,
}
impl Future for CounterFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.count += 1;
if self.count >= 2 {
Poll::Ready(())
} else {
let waker = cx.waker().clone();
*self.waker.lock().unwrap() = Some(waker);
Poll::Pending
}
}
}
```
The executor calls `Task::poll` which calls `TaskAlloc::run_future`. The future returns `Poll::Pending` on the first poll. If we wake the Waker using the shared state the future runs again, however, before the `poll` function could call `finish_running` on the state, the kernel preempts it. Now, if the other thread wakes up the `Waker` through the shared state again, we call `Task::schedule` which would then call `Remote::schedule` whose initial check to see is the state is `COMPLETED` will fail because the executor was prempted earlier on. It will proceed to push the task in the `sync-queue`. The executor will end up polling it again and since it does not check whether the state was `COMPLETED` before calling `TaskAlloc::run_future` it will lead to `run_future` seeing `Poll::Ready` immediately in which case it drops the future again.
I think the fix would be to add an `is_completed` check in `Task::run` along with the `is_cancelled` check that is already there.
Please correct me if i am missing something but happy to make a PR is it is a genuine fix.
关闭于 2026-04-10 8 条评论