1
Fork 0

sync::mpsc: prevent double free on Drop

This PR is fixing a regression introduced by #121646 that can lead to a
double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR
https://github.com/crossbeam-rs/crossbeam/pull/1187

Signed-off-by: Petros Angelatos <petrosagg@gmail.com>
This commit is contained in:
Petros Angelatos 2025-04-08 22:37:25 +03:00
parent 9eb6a5446a
commit b9e2ac5c7b
2 changed files with 8 additions and 2 deletions

View file

@ -569,9 +569,15 @@ impl<T> Channel<T> {
// In that case, just wait until it gets initialized.
while block.is_null() {
backoff.spin_heavy();
block = self.head.block.load(Ordering::Acquire);
block = self.head.block.swap(ptr::null_mut(), Ordering::AcqRel);
}
}
// After this point `head.block` is not modified again and it will be deallocated if it's
// non-null. The `Drop` code of the channel, which runs after this function, also attempts
// to deallocate `head.block` if it's non-null. Therefore this function must maintain the
// invariant that if a deallocation of head.block is attemped then it must also be set to
// NULL. Failing to do so will lead to the Drop code attempting a double free. For this
// reason both reads above do an atomic swap instead of a simple atomic load.
unsafe {
// Drop all messages between head and tail and deallocate the heap-allocated blocks.

View file

@ -38,7 +38,7 @@ fn main() {
// call returns the channel state has tail.index = head.index, tail.block = NULL, and
// head.block != NULL.
t1.join().unwrap();
// 6. The last sender (s1) is dropped here which also attempts to cleanup any data in the
// 6. The last sender (s2) is dropped here which also attempts to cleanup any data in the
// channel. It observes `tail.index = head.index` and so it doesn't attempt to cleanup any
// messages but it also observes that `head.block != NULL` and attempts to deallocate it.
// This is however already deallocated by `discard_all_messages`, leading to a double free.