Skip to content

Commit

Permalink
Auto merge of #114795 - RalfJung:cell-swap, r=dtolnay
Browse files Browse the repository at this point in the history
make Cell::swap panic if the Cells partially overlap

The following function ought to be sound:
```rust
fn as_cell_of_array<T, const N: usize>(c: &[Cell<T>; N]) -> &Cell<[T; N]> {
    unsafe { transmute(c) }
}
```
However, due to `Cell::swap`, it currently is not -- safe code can [cause a use-after-free](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=c9415799722d985ff7d2c2c997b724ca). This PR fixes that.

Fixes #80778
  • Loading branch information
bors committed Aug 29, 2023
2 parents cedbe5c + e7a1e42 commit f6faef4
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 3 deletions.
17 changes: 15 additions & 2 deletions library/core/src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@

use crate::cmp::Ordering;
use crate::fmt::{self, Debug, Display};
use crate::intrinsics::is_nonoverlapping;
use crate::marker::{PhantomData, Unsize};
use crate::mem;
use crate::ops::{CoerceUnsized, Deref, DerefMut, DispatchFromDyn};
Expand Down Expand Up @@ -415,6 +416,12 @@ impl<T> Cell<T> {
/// Swaps the values of two `Cell`s.
/// Difference with `std::mem::swap` is that this function doesn't require `&mut` reference.
///
/// # Panics
///
/// This function will panic if `self` and `other` are different `Cell`s that partially overlap.
/// (Using just standard library methods, it is impossible to create such partially overlapping `Cell`s.
/// However, unsafe code is allowed to e.g. create two `&Cell<[i32; 2]>` that partially overlap.)
///
/// # Examples
///
/// ```
Expand All @@ -430,14 +437,20 @@ impl<T> Cell<T> {
#[stable(feature = "move_cell", since = "1.17.0")]
pub fn swap(&self, other: &Self) {
if ptr::eq(self, other) {
// Swapping wouldn't change anything.
return;
}
if !is_nonoverlapping(self, other, 1) {
// See <https://github.com/rust-lang/rust/issues/80778> for why we need to stop here.
panic!("`Cell::swap` on overlapping non-identical `Cell`s");
}
// SAFETY: This can be risky if called from separate threads, but `Cell`
// is `!Sync` so this won't happen. This also won't invalidate any
// pointers since `Cell` makes sure nothing else will be pointing into
// either of these `Cell`s.
// either of these `Cell`s. We also excluded shenanigans like partially overlapping `Cell`s,
// so `swap` will just properly copy two full values of type `T` back and forth.
unsafe {
ptr::swap(self.value.get(), other.value.get());
mem::swap(&mut *self.value.get(), &mut *other.value.get());
}
}

Expand Down
2 changes: 1 addition & 1 deletion library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2567,7 +2567,7 @@ pub(crate) fn is_nonoverlapping<T>(src: *const T, dst: *const T, count: usize) -
let size = mem::size_of::<T>()
.checked_mul(count)
.expect("is_nonoverlapping: `size_of::<T>() * count` overflows a usize");
let diff = if src_usize > dst_usize { src_usize - dst_usize } else { dst_usize - src_usize };
let diff = src_usize.abs_diff(dst_usize);
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
// they do not overlap.
diff >= size
Expand Down

0 comments on commit f6faef4

Please sign in to comment.