Skip to content

Commit e458255

Browse files
committed
Fix possible use after free in clear functions
The `drop` test of `deque` is renamed to `test_drop` to avoid name conflicts with the `std::mem::drop` function. Fix #646
1 parent 45a1f11 commit e458255

File tree

5 files changed

+181
-24
lines changed

5 files changed

+181
-24
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
9+
10+
- Fixed unsoundness in `Deque::clear`, `HistoryBuf::clear` and `IndexMap::clear` in the context
11+
of panicking drop implementations.
912
- Added `from_bytes_truncating_at_nul` to `CString`
1013
- Added `CString::{into_bytes, into_bytes_with_nul, into_string}`
1114
- Added `pop_front_if` and `pop_back_if` to `Deque`

src/deque.rs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,18 @@ impl<T, S: VecStorage<T> + ?Sized> DequeInner<T, S> {
243243

244244
/// Clears the deque, removing all values.
245245
pub fn clear(&mut self) {
246-
// safety: we're immediately setting a consistent empty state.
247-
unsafe { self.drop_contents() }
248-
self.front = 0;
249-
self.back = 0;
250-
self.full = false;
246+
struct Guard<'a, T, S: VecStorage<T> + ?Sized>(&'a mut DequeInner<T, S>);
247+
impl<'a, T, S: VecStorage<T> + ?Sized> Drop for Guard<'a, T, S> {
248+
fn drop(&mut self) {
249+
self.0.front = 0;
250+
self.0.back = 0;
251+
self.0.full = false;
252+
}
253+
}
254+
let this = Guard(self);
255+
// SAFETY: Guard will be dropped and lead to a consistent empty state even in the case of a
256+
// panic leading to unwinding during the dropping of an item
257+
unsafe { this.0.drop_contents() }
251258
}
252259

253260
/// Drop all items in the `Deque`, leaving the state `back/front/full` unmodified.
@@ -1306,6 +1313,12 @@ impl<T, const NS: usize, const ND: usize> TryFrom<[T; NS]> for Deque<T, ND> {
13061313

13071314
#[cfg(test)]
13081315
mod tests {
1316+
use std::{
1317+
mem::ManuallyDrop,
1318+
panic::{catch_unwind, AssertUnwindSafe},
1319+
sync::atomic::{AtomicI32, Ordering::Relaxed},
1320+
};
1321+
13091322
use super::Deque;
13101323
use crate::CapacityError;
13111324
use static_assertions::assert_not_impl_any;
@@ -1324,7 +1337,7 @@ mod tests {
13241337
}
13251338

13261339
#[test]
1327-
fn drop() {
1340+
fn test_drop() {
13281341
droppable!();
13291342

13301343
{
@@ -1629,20 +1642,23 @@ mod tests {
16291642

16301643
#[test]
16311644
fn clear() {
1632-
let mut q: Deque<i32, 4> = Deque::new();
1645+
droppable!();
1646+
let mut q: Deque<Droppable, 4> = Deque::new();
16331647
assert_eq!(q.len(), 0);
16341648

1635-
q.push_back(0).unwrap();
1636-
q.push_back(1).unwrap();
1637-
q.push_back(2).unwrap();
1638-
q.push_back(3).unwrap();
1649+
q.push_back(Droppable::new()).unwrap();
1650+
q.push_back(Droppable::new()).unwrap();
1651+
q.push_back(Droppable::new()).unwrap();
1652+
q.push_back(Droppable::new()).unwrap();
16391653
assert_eq!(q.len(), 4);
16401654

16411655
q.clear();
16421656
assert_eq!(q.len(), 0);
1657+
assert_eq!(Droppable::count(), 0);
16431658

1644-
q.push_back(0).unwrap();
1659+
q.push_back(Droppable::new()).unwrap();
16451660
assert_eq!(q.len(), 1);
1661+
assert_eq!(Droppable::count(), 1);
16461662
}
16471663

16481664
#[test]
@@ -2275,4 +2291,42 @@ mod tests {
22752291
assert_eq!(deq.pop_back_if(|_| true), None);
22762292
assert!(deq.is_empty());
22772293
}
2294+
2295+
#[test]
2296+
fn test_use_after_free_clear() {
2297+
// See https://github.com/rust-embedded/heapless/issues/646
2298+
2299+
static COUNT: AtomicI32 = AtomicI32::new(0);
2300+
2301+
#[derive(Debug)]
2302+
#[allow(unused)]
2303+
struct Dropper();
2304+
2305+
impl Dropper {
2306+
fn new() -> Self {
2307+
COUNT.fetch_add(1, Relaxed);
2308+
Self()
2309+
}
2310+
fn count() -> i32 {
2311+
COUNT.load(Relaxed)
2312+
}
2313+
}
2314+
impl Drop for Dropper {
2315+
fn drop(&mut self) {
2316+
COUNT.fetch_sub(1, Relaxed);
2317+
panic!("Testing panicking");
2318+
}
2319+
}
2320+
2321+
let mut deque = Deque::<Dropper, 5>::new();
2322+
deque.push_back(Dropper::new()).unwrap();
2323+
// Don't panic on dropping of the deque
2324+
let mut deque = ManuallyDrop::new(deque);
2325+
let mut unwind_safe_deque = AssertUnwindSafe(&mut deque);
2326+
2327+
catch_unwind(move || unwind_safe_deque.clear()).unwrap_err();
2328+
assert_eq!(deque.len(), 0);
2329+
deque.clear();
2330+
assert_eq!(Dropper::count(), 0);
2331+
}
22782332
}

src/history_buf.rs

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,24 +308,30 @@ impl<T: Copy, S: HistoryBufStorage<T> + ?Sized> HistoryBufInner<T, S> {
308308

309309
/// Clears the buffer, replacing every element with the given value.
310310
pub fn clear_with(&mut self, t: T) {
311-
// SAFETY: we reset the values just after
312-
unsafe { self.drop_contents() };
313-
self.write_at = 0;
314-
self.filled = true;
311+
self.clear();
315312

316313
for d in self.data.borrow_mut() {
317314
*d = MaybeUninit::new(t);
318315
}
316+
self.write_at = 0;
317+
self.filled = true;
319318
}
320319
}
321320

322321
impl<T, S: HistoryBufStorage<T> + ?Sized> HistoryBufInner<T, S> {
323322
/// Clears the buffer
324323
pub fn clear(&mut self) {
325-
// SAFETY: we reset the values just after
326-
unsafe { self.drop_contents() };
327-
self.write_at = 0;
328-
self.filled = false;
324+
struct Guard<'a, T, S: HistoryBufStorage<T> + ?Sized>(&'a mut HistoryBufInner<T, S>);
325+
impl<'a, T, S: HistoryBufStorage<T> + ?Sized> Drop for Guard<'a, T, S> {
326+
fn drop(&mut self) {
327+
self.0.write_at = 0;
328+
self.0.filled = false;
329+
}
330+
}
331+
let this = Guard(self);
332+
// SAFETY: Guard will be dropped and lead to a consistent empty state even in the case of a
333+
// panic leading to unwinding during the dropping of an item
334+
unsafe { this.0.drop_contents() };
329335
}
330336
}
331337

@@ -659,6 +665,11 @@ mod tests {
659665
fmt::Debug,
660666
sync::atomic::{AtomicUsize, Ordering},
661667
};
668+
use std::{
669+
mem::ManuallyDrop,
670+
panic::{catch_unwind, AssertUnwindSafe},
671+
sync::atomic::AtomicI32,
672+
};
662673

663674
use static_assertions::assert_not_impl_any;
664675

@@ -983,6 +994,44 @@ mod tests {
983994
}
984995
}
985996

997+
#[test]
998+
fn test_use_after_free_clear() {
999+
// See https://github.com/rust-embedded/heapless/issues/646
1000+
1001+
static COUNT: AtomicI32 = AtomicI32::new(0);
1002+
1003+
#[derive(Debug)]
1004+
#[allow(unused)]
1005+
struct Dropper();
1006+
1007+
impl Dropper {
1008+
fn new() -> Self {
1009+
COUNT.fetch_add(1, Ordering::Relaxed);
1010+
Self()
1011+
}
1012+
fn count() -> i32 {
1013+
COUNT.load(Ordering::Relaxed)
1014+
}
1015+
}
1016+
impl Drop for Dropper {
1017+
fn drop(&mut self) {
1018+
COUNT.fetch_sub(1, Ordering::Relaxed);
1019+
panic!("Testing panicking");
1020+
}
1021+
}
1022+
1023+
let mut history_buf = HistoryBuf::<Dropper, 5>::new();
1024+
history_buf.write(Dropper::new());
1025+
// Don't panic on dropping of the history_buf
1026+
let mut history_buf = ManuallyDrop::new(history_buf);
1027+
let mut unwind_safe_history_buf = AssertUnwindSafe(&mut history_buf);
1028+
1029+
catch_unwind(move || unwind_safe_history_buf.clear()).unwrap_err();
1030+
assert_eq!(history_buf.len(), 0);
1031+
history_buf.clear();
1032+
assert_eq!(Dropper::count(), 0);
1033+
}
1034+
9861035
fn _test_variance<'a: 'b, 'b>(x: HistoryBuf<&'a (), 42>) -> HistoryBuf<&'b (), 42> {
9871036
x
9881037
}

src/index_map.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -978,10 +978,18 @@ impl<K, V, S, const N: usize> IndexMap<K, V, S, N> {
978978
/// assert!(a.is_empty());
979979
/// ```
980980
pub fn clear(&mut self) {
981-
self.core.entries.clear();
982-
for pos in self.core.indices.iter_mut() {
983-
*pos = None;
981+
struct Guard<'a, K, V, S, const N: usize>(&'a mut IndexMap<K, V, S, N>);
982+
impl<'a, K, V, S, const N: usize> Drop for Guard<'a, K, V, S, N> {
983+
fn drop(&mut self) {
984+
for pos in self.0.core.indices.iter_mut() {
985+
*pos = None;
986+
}
987+
}
984988
}
989+
let this = Guard(self);
990+
// SAFETY: Guard will be dropped and lead to a consistent empty state even in the case of a
991+
// panic leading to unwinding during the dropping of an item
992+
this.0.core.entries.clear();
985993
}
986994
}
987995

@@ -1617,6 +1625,11 @@ where
16171625
#[cfg(test)]
16181626
mod tests {
16191627
use core::mem;
1628+
use std::{
1629+
mem::ManuallyDrop,
1630+
panic::{catch_unwind, AssertUnwindSafe},
1631+
sync::atomic::{AtomicI32, Ordering},
1632+
};
16201633

16211634
use static_assertions::assert_not_impl_any;
16221635

@@ -2058,4 +2071,42 @@ mod tests {
20582071
assert_eq!(map.len(), 1);
20592072
assert_eq!(map.get(&1), Some(&10));
20602073
}
2074+
2075+
#[test]
2076+
fn test_use_after_free_clear() {
2077+
// See https://github.com/rust-embedded/heapless/issues/646
2078+
2079+
static COUNT: AtomicI32 = AtomicI32::new(0);
2080+
2081+
#[derive(Debug, Hash, PartialEq, Eq)]
2082+
#[allow(unused)]
2083+
struct Dropper();
2084+
2085+
impl Dropper {
2086+
fn new() -> Self {
2087+
COUNT.fetch_add(1, Ordering::Relaxed);
2088+
Self()
2089+
}
2090+
fn count() -> i32 {
2091+
COUNT.load(Ordering::Relaxed)
2092+
}
2093+
}
2094+
impl Drop for Dropper {
2095+
fn drop(&mut self) {
2096+
COUNT.fetch_sub(1, Ordering::Relaxed);
2097+
panic!("Testing panicking");
2098+
}
2099+
}
2100+
2101+
let mut history_buf = FnvIndexMap::<Dropper, u32, 8>::new();
2102+
history_buf.insert(Dropper::new(), 0).unwrap();
2103+
// Don't panic on dropping of the history_buf
2104+
let mut history_buf = ManuallyDrop::new(history_buf);
2105+
let mut unwind_safe_history_buf = AssertUnwindSafe(&mut history_buf);
2106+
2107+
catch_unwind(move || unwind_safe_history_buf.clear()).unwrap_err();
2108+
assert_eq!(history_buf.len(), 0);
2109+
history_buf.clear();
2110+
assert_eq!(Dropper::count(), 0);
2111+
}
20612112
}

src/test_helpers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ macro_rules! droppable {
22
() => {
33
static COUNT: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0);
44

5-
#[derive(Eq, Ord, PartialEq, PartialOrd)]
5+
#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Hash)]
66
struct Droppable(i32);
77
impl Droppable {
88
fn new() -> Self {

0 commit comments

Comments
 (0)