Skip to content

Commit 83a2a98

Browse files
committed
Fix double-drop in RawTable::clone_from
If an element from the destination table panics from its `Drop` impl then elements from that table would end up being dropped twice, which is unsound.
1 parent 0ea54de commit 83a2a98

File tree

3 files changed

+120
-40
lines changed

3 files changed

+120
-40
lines changed

src/map.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8361,4 +8361,48 @@ mod test_map {
83618361
let ys = map.get_many_key_value_mut(["baz", "baz"]);
83628362
assert_eq!(ys, None);
83638363
}
8364+
8365+
#[test]
8366+
#[should_panic = "panic in drop"]
8367+
fn test_clone_from_double_drop() {
8368+
#[derive(Clone)]
8369+
struct CheckedDrop {
8370+
panic_in_drop: bool,
8371+
dropped: bool,
8372+
}
8373+
impl Drop for CheckedDrop {
8374+
fn drop(&mut self) {
8375+
if self.panic_in_drop {
8376+
self.dropped = true;
8377+
panic!("panic in drop");
8378+
}
8379+
if self.dropped {
8380+
panic!("double drop");
8381+
}
8382+
self.dropped = true;
8383+
}
8384+
}
8385+
const DISARMED: CheckedDrop = CheckedDrop {
8386+
panic_in_drop: false,
8387+
dropped: false,
8388+
};
8389+
const ARMED: CheckedDrop = CheckedDrop {
8390+
panic_in_drop: true,
8391+
dropped: false,
8392+
};
8393+
8394+
let mut map1 = HashMap::new();
8395+
map1.insert(1, DISARMED);
8396+
map1.insert(2, DISARMED);
8397+
map1.insert(3, DISARMED);
8398+
map1.insert(4, DISARMED);
8399+
8400+
let mut map2 = HashMap::new();
8401+
map2.insert(1, DISARMED);
8402+
map2.insert(2, ARMED);
8403+
map2.insert(3, DISARMED);
8404+
map2.insert(4, DISARMED);
8405+
8406+
map2.clone_from(&map1);
8407+
}
83648408
}

src/raw/mod.rs

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::alloc::alloc::{handle_alloc_error, Layout};
2-
use crate::scopeguard::guard;
2+
use crate::scopeguard::{guard, ScopeGuard};
33
use crate::TryReserveError;
44
use core::iter::FusedIterator;
55
use core::marker::PhantomData;
@@ -1602,25 +1602,27 @@ impl<T: Clone, A: Allocator + Clone> Clone for RawTable<T, A> {
16021602
Self::new_in(self.table.alloc.clone())
16031603
} else {
16041604
unsafe {
1605-
let mut new_table = ManuallyDrop::new(
1606-
// Avoid `Result::ok_or_else` because it bloats LLVM IR.
1607-
match Self::new_uninitialized(
1608-
self.table.alloc.clone(),
1609-
self.table.buckets(),
1610-
Fallibility::Infallible,
1611-
) {
1612-
Ok(table) => table,
1613-
Err(_) => hint::unreachable_unchecked(),
1614-
},
1615-
);
1616-
1617-
new_table.clone_from_spec(self, |new_table| {
1618-
// We need to free the memory allocated for the new table.
1605+
// Avoid `Result::ok_or_else` because it bloats LLVM IR.
1606+
let new_table = match Self::new_uninitialized(
1607+
self.table.alloc.clone(),
1608+
self.table.buckets(),
1609+
Fallibility::Infallible,
1610+
) {
1611+
Ok(table) => table,
1612+
Err(_) => hint::unreachable_unchecked(),
1613+
};
1614+
1615+
// If cloning fails then we need to free the allocation for the
1616+
// new table. However we don't run its drop since its control
1617+
// bytes are not initialized yet.
1618+
let mut guard = guard(ManuallyDrop::new(new_table), |new_table| {
16191619
new_table.free_buckets();
16201620
});
16211621

1622-
// Return the newly created table.
1623-
ManuallyDrop::into_inner(new_table)
1622+
guard.clone_from_spec(self);
1623+
1624+
// Disarm the scope guard and return the newly created table.
1625+
ManuallyDrop::into_inner(ScopeGuard::into_inner(guard))
16241626
}
16251627
}
16261628
}
@@ -1630,19 +1632,30 @@ impl<T: Clone, A: Allocator + Clone> Clone for RawTable<T, A> {
16301632
*self = Self::new_in(self.table.alloc.clone());
16311633
} else {
16321634
unsafe {
1633-
// First, drop all our elements without clearing the control bytes.
1634-
self.drop_elements();
1635+
// Make sure that if any panics occurs, we clear the table and
1636+
// leave it in an empty state.
1637+
let mut self_ = guard(self, |self_| {
1638+
self_.clear_no_drop();
1639+
});
1640+
1641+
// First, drop all our elements without clearing the control
1642+
// bytes. If this panics then the scope guard will clear the
1643+
// table, leaking any elements that were not dropped yet.
1644+
//
1645+
// This leak is unavoidable: we can't try dropping more elements
1646+
// since this could lead to another panic and abort the process.
1647+
self_.drop_elements();
16351648

16361649
// If necessary, resize our table to match the source.
1637-
if self.buckets() != source.buckets() {
1650+
if self_.buckets() != source.buckets() {
16381651
// Skip our drop by using ptr::write.
1639-
if !self.table.is_empty_singleton() {
1640-
self.free_buckets();
1652+
if !self_.table.is_empty_singleton() {
1653+
self_.free_buckets();
16411654
}
1642-
(self as *mut Self).write(
1655+
(&mut **self_ as *mut Self).write(
16431656
// Avoid `Result::unwrap_or_else` because it bloats LLVM IR.
16441657
match Self::new_uninitialized(
1645-
self.table.alloc.clone(),
1658+
self_.table.alloc.clone(),
16461659
source.buckets(),
16471660
Fallibility::Infallible,
16481661
) {
@@ -1652,31 +1665,31 @@ impl<T: Clone, A: Allocator + Clone> Clone for RawTable<T, A> {
16521665
);
16531666
}
16541667

1655-
self.clone_from_spec(source, |self_| {
1656-
// We need to leave the table in an empty state.
1657-
self_.clear_no_drop();
1658-
});
1668+
self_.clone_from_spec(source);
1669+
1670+
// Disarm the scope guard if cloning was successful.
1671+
ScopeGuard::into_inner(self_);
16591672
}
16601673
}
16611674
}
16621675
}
16631676

16641677
/// Specialization of `clone_from` for `Copy` types
16651678
trait RawTableClone {
1666-
unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self));
1679+
unsafe fn clone_from_spec(&mut self, source: &Self);
16671680
}
16681681
impl<T: Clone, A: Allocator + Clone> RawTableClone for RawTable<T, A> {
16691682
default_fn! {
16701683
#[cfg_attr(feature = "inline-more", inline)]
1671-
unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)) {
1672-
self.clone_from_impl(source, on_panic);
1684+
unsafe fn clone_from_spec(&mut self, source: &Self) {
1685+
self.clone_from_impl(source);
16731686
}
16741687
}
16751688
}
16761689
#[cfg(feature = "nightly")]
16771690
impl<T: Copy, A: Allocator + Clone> RawTableClone for RawTable<T, A> {
16781691
#[cfg_attr(feature = "inline-more", inline)]
1679-
unsafe fn clone_from_spec(&mut self, source: &Self, _on_panic: impl FnMut(&mut Self)) {
1692+
unsafe fn clone_from_spec(&mut self, source: &Self) {
16801693
source
16811694
.table
16821695
.ctrl(0)
@@ -1691,9 +1704,12 @@ impl<T: Copy, A: Allocator + Clone> RawTableClone for RawTable<T, A> {
16911704
}
16921705

16931706
impl<T: Clone, A: Allocator + Clone> RawTable<T, A> {
1694-
/// Common code for clone and clone_from. Assumes `self.buckets() == source.buckets()`.
1707+
/// Common code for clone and clone_from. Assumes:
1708+
/// - `self.buckets() == source.buckets()`.
1709+
/// - Any existing elements have been dropped.
1710+
/// - The control bytes are not initialized yet.
16951711
#[cfg_attr(feature = "inline-more", inline)]
1696-
unsafe fn clone_from_impl(&mut self, source: &Self, mut on_panic: impl FnMut(&mut Self)) {
1712+
unsafe fn clone_from_impl(&mut self, source: &Self) {
16971713
// Copy the control bytes unchanged. We do this in a single pass
16981714
source
16991715
.table
@@ -1711,11 +1727,6 @@ impl<T: Clone, A: Allocator + Clone> RawTable<T, A> {
17111727
}
17121728
}
17131729
}
1714-
1715-
// Depending on whether we were called from clone or clone_from, we
1716-
// either need to free the memory for the destination table or just
1717-
// clear the control bytes.
1718-
on_panic(self_);
17191730
});
17201731

17211732
for from in source.iter() {

src/scopeguard.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
// Extracted from the scopeguard crate
2-
use core::ops::{Deref, DerefMut};
2+
use core::{
3+
mem,
4+
ops::{Deref, DerefMut},
5+
ptr,
6+
};
37

48
pub struct ScopeGuard<T, F>
59
where
@@ -17,6 +21,27 @@ where
1721
ScopeGuard { dropfn, value }
1822
}
1923

24+
impl<T, F> ScopeGuard<T, F>
25+
where
26+
F: FnMut(&mut T),
27+
{
28+
#[inline]
29+
pub fn into_inner(guard: Self) -> T {
30+
// Cannot move out of Drop-implementing types, so
31+
// ptr::read the value and forget the guard.
32+
unsafe {
33+
let value = ptr::read(&guard.value);
34+
// read the closure so that it is dropped, and assign it to a local
35+
// variable to ensure that it is only dropped after the guard has
36+
// been forgotten. (In case the Drop impl of the closure, or that
37+
// of any consumed captured variable, panics).
38+
let _dropfn = ptr::read(&guard.dropfn);
39+
mem::forget(guard);
40+
value
41+
}
42+
}
43+
}
44+
2045
impl<T, F> Deref for ScopeGuard<T, F>
2146
where
2247
F: FnMut(&mut T),

0 commit comments

Comments
 (0)