Skip to content

Commit 1c46649

Browse files
committed
Fix double free in IntoIter
**Description** - The previous implementation of `deallocate_leaves` was faulty because it would read all the way through the linked list of leaf nodes even if some leaves had been popped off the back by the iterator. - Fixing this by changing the `deallocate_leaves` take an iterator with start and end, instead of just start pointer. - Added some missing safety docs - Modified the `IntoIter` fuzz tests to iterate on the front and the back - Added new unit test to ensure drop count was correct **Testing Done** Ran fuzzer and full test script
1 parent 17d5ff3 commit 1c46649

File tree

4 files changed

+215
-32
lines changed

4 files changed

+215
-32
lines changed

fuzz/fuzz_targets/fuzz_tree_map_api.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ enum Action {
4444
EntryRef(EntryAction, Box<[u8]>),
4545
Fuzzy(Box<[u8]>),
4646
Prefix(Box<[u8]>),
47-
IntoIter(usize),
47+
IntoIter { take_front: usize, take_back: usize },
4848
}
4949

5050
libfuzzer_sys::fuzz_target!(|actions: Vec<Action>| {
@@ -221,10 +221,23 @@ libfuzzer_sys::fuzz_target!(|actions: Vec<Action>| {
221221
let v: Vec<_> = tree.prefix(&key).collect();
222222
std::hint::black_box(v);
223223
},
224-
Action::IntoIter(take) => {
224+
Action::IntoIter {
225+
take_front,
226+
take_back,
227+
} => {
225228
let tree = mem::replace(&mut tree, TreeMap::<_, u32>::new());
226-
let num_entries = tree.into_iter().take(take).count();
227-
assert!(num_entries <= take);
229+
230+
let original_size = tree.len();
231+
let mut it = tree.into_iter();
232+
let front_count = it.by_ref().take(take_front).count();
233+
let back_count = it.rev().take(take_back).count();
234+
235+
assert_eq!(
236+
front_count + back_count,
237+
original_size.min(take_front.saturating_add(take_back))
238+
);
239+
assert!(front_count <= take_front);
240+
assert!(back_count <= take_back);
228241
},
229242
}
230243
}

src/collections/map/iterators/into_iter.rs

Lines changed: 170 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::mem::ManuallyDrop;
1+
use std::mem::{self, ManuallyDrop};
22

33
use crate::{deallocate_leaves, deallocate_tree_non_leaves, NodePtr, RawIterator, TreeMap};
44

@@ -17,13 +17,11 @@ pub struct IntoIter<K, V, const PREFIX_LEN: usize> {
1717

1818
impl<K, V, const PREFIX_LEN: usize> Drop for IntoIter<K, V, PREFIX_LEN> {
1919
fn drop(&mut self) {
20-
if let Some(next) = unsafe { self.inner.next() } {
21-
// SAFETY: TODO
22-
unsafe { deallocate_leaves(next) }
23-
}
20+
// SAFETY: The `deallocate_tree_non_leaves` function is called earlier on the
21+
// trie (which we have unique access to), so the leaves will still be allocated.
22+
unsafe { deallocate_leaves(mem::replace(&mut self.inner, RawIterator::empty())) }
2423

25-
// Just to be safe, clear the iterator
26-
self.inner = RawIterator::empty();
24+
// Just to be safe, clear the iterator size
2725
self.size = 0;
2826
}
2927
}
@@ -35,6 +33,8 @@ impl<K, V, const PREFIX_LEN: usize> IntoIter<K, V, PREFIX_LEN> {
3533
let tree = ManuallyDrop::new(tree);
3634

3735
if let Some(state) = &tree.state {
36+
// SAFETY: By construction (and maintained on insert/delete), the `min_leaf` is
37+
// always before or equal to `max_leaf` in the leaf node order.
3838
let inner = unsafe { RawIterator::new(state.min_leaf, state.max_leaf) };
3939

4040
// SAFETY: Since this function takes an owned `TreeMap`, we can assume there is
@@ -58,11 +58,13 @@ impl<K, V, const PREFIX_LEN: usize> Iterator for IntoIter<K, V, PREFIX_LEN> {
5858
type Item = (K, V);
5959

6060
fn next(&mut self) -> Option<Self::Item> {
61-
// SAFETY: TODO
61+
// SAFETY: By construction of the `IntoIter` we know that we have ownership over
62+
// the trie, and there will be no other concurrent access (read or write).
6263
let leaf_ptr = unsafe { self.inner.next() };
6364

6465
leaf_ptr.map(|leaf_ptr| {
65-
// SAFETY: TODO
66+
// SAFETY: This function is only called once for a given `leaf_ptr` since the
67+
// iterator will never repeat
6668
unsafe { NodePtr::deallocate_node_ptr(leaf_ptr) }.into_entry()
6769
})
6870
}
@@ -74,20 +76,24 @@ impl<K, V, const PREFIX_LEN: usize> Iterator for IntoIter<K, V, PREFIX_LEN> {
7476

7577
impl<K, V, const PREFIX_LEN: usize> DoubleEndedIterator for IntoIter<K, V, PREFIX_LEN> {
7678
fn next_back(&mut self) -> Option<Self::Item> {
77-
// SAFETY: TODO
79+
// SAFETY: By construction of the `IntoIter` we know that we have ownership over
80+
// the trie, and there will be no other concurrent access (read or write).
7881
let leaf_ptr = unsafe { self.inner.next_back() };
7982

8083
leaf_ptr.map(|leaf_ptr| {
81-
// SAFETY: TODO
84+
// SAFETY: This function is only called once for a given `leaf_ptr` since the
85+
// iterator will never repeat
8286
unsafe { NodePtr::deallocate_node_ptr(leaf_ptr) }.into_entry()
8387
})
8488
}
8589
}
8690

87-
/// An owning iterator over the keys of a `TreeMap`.
91+
impl<K, V, const PREFIX_LEN: usize> ExactSizeIterator for IntoIter<K, V, PREFIX_LEN> {}
92+
93+
/// An owning iterator over the keys of a [`TreeMap`].
8894
///
8995
/// This `struct` is created by the [`crate::TreeMap::into_keys`] method on
90-
/// `TreeMap`. See its documentation for more.
96+
/// [`TreeMap`]. See its documentation for more.
9197
pub struct IntoKeys<K, V, const PREFIX_LEN: usize>(IntoIter<K, V, PREFIX_LEN>);
9298

9399
impl<K, V, const PREFIX_LEN: usize> IntoKeys<K, V, PREFIX_LEN> {
@@ -102,6 +108,10 @@ impl<K, V, const PREFIX_LEN: usize> Iterator for IntoKeys<K, V, PREFIX_LEN> {
102108
fn next(&mut self) -> Option<Self::Item> {
103109
Some(self.0.next()?.0)
104110
}
111+
112+
fn size_hint(&self) -> (usize, Option<usize>) {
113+
self.0.size_hint()
114+
}
105115
}
106116

107117
impl<K, V, const PREFIX_LEN: usize> DoubleEndedIterator for IntoKeys<K, V, PREFIX_LEN> {
@@ -110,9 +120,11 @@ impl<K, V, const PREFIX_LEN: usize> DoubleEndedIterator for IntoKeys<K, V, PREFI
110120
}
111121
}
112122

113-
/// An owning iterator over the values of a `TreeMap`.
123+
impl<K, V, const PREFIX_LEN: usize> ExactSizeIterator for IntoKeys<K, V, PREFIX_LEN> {}
124+
125+
/// An owning iterator over the values of a [`TreeMap`].
114126
///
115-
/// This `struct` is created by the [`into_values`] method on `TreeMap`.
127+
/// This `struct` is created by the [`into_values`] method on [`TreeMap`].
116128
/// See its documentation for more.
117129
///
118130
/// [`into_values`]: crate::TreeMap::into_values
@@ -130,10 +142,153 @@ impl<K, V, const PREFIX_LEN: usize> Iterator for IntoValues<K, V, PREFIX_LEN> {
130142
fn next(&mut self) -> Option<Self::Item> {
131143
Some(self.0.next()?.1)
132144
}
145+
146+
fn size_hint(&self) -> (usize, Option<usize>) {
147+
self.0.size_hint()
148+
}
133149
}
134150

135151
impl<K, V, const PREFIX_LEN: usize> DoubleEndedIterator for IntoValues<K, V, PREFIX_LEN> {
136152
fn next_back(&mut self) -> Option<Self::Item> {
137153
Some(self.0.next_back()?.1)
138154
}
139155
}
156+
157+
impl<K, V, const PREFIX_LEN: usize> ExactSizeIterator for IntoValues<K, V, PREFIX_LEN> {}
158+
159+
#[cfg(test)]
160+
mod tests {
161+
use std::sync::{
162+
atomic::{AtomicUsize, Ordering},
163+
Arc,
164+
};
165+
166+
use crate::{AsBytes, NoPrefixesBytes, OrderedBytes};
167+
168+
use super::*;
169+
170+
#[derive(Debug)]
171+
struct DropCounter<T>(Arc<AtomicUsize>, T);
172+
173+
impl<T> DropCounter<T> {
174+
fn new(counter: &Arc<AtomicUsize>, value: T) -> Self {
175+
counter.fetch_add(1, Ordering::Relaxed);
176+
DropCounter(Arc::clone(counter), value)
177+
}
178+
}
179+
180+
impl<T> Drop for DropCounter<T> {
181+
fn drop(&mut self) {
182+
self.0.fetch_sub(1, Ordering::Relaxed);
183+
}
184+
}
185+
186+
impl<T: Ord> Ord for DropCounter<T> {
187+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
188+
self.1.cmp(&other.1)
189+
}
190+
}
191+
192+
impl<T: PartialOrd> PartialOrd for DropCounter<T> {
193+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
194+
self.1.partial_cmp(&other.1)
195+
}
196+
}
197+
198+
impl<T: Eq> Eq for DropCounter<T> {}
199+
200+
impl<T: PartialEq> PartialEq for DropCounter<T> {
201+
fn eq(&self, other: &Self) -> bool {
202+
self.1 == other.1
203+
}
204+
}
205+
206+
impl<T: AsBytes> AsBytes for DropCounter<T> {
207+
fn as_bytes(&self) -> &[u8] {
208+
self.1.as_bytes()
209+
}
210+
}
211+
212+
unsafe impl<T: NoPrefixesBytes> NoPrefixesBytes for DropCounter<T> {}
213+
unsafe impl<T: OrderedBytes + Ord> OrderedBytes for DropCounter<T> {}
214+
215+
fn setup_will_deallocate_unconsumed_iter_values(
216+
drop_counter: &Arc<AtomicUsize>,
217+
) -> TreeMap<DropCounter<[u8; 3]>, usize> {
218+
fn swap<A, B>((a, b): (A, B)) -> (B, A) {
219+
(b, a)
220+
}
221+
222+
assert_eq!(drop_counter.load(Ordering::Relaxed), 0);
223+
224+
[
225+
[0u8, 0u8, 0u8],
226+
[0, 0, u8::MAX],
227+
[0, u8::MAX, 0],
228+
[0, u8::MAX, u8::MAX],
229+
[u8::MAX, 0, 0],
230+
[u8::MAX, 0, u8::MAX],
231+
[u8::MAX, u8::MAX, 0],
232+
[u8::MAX, u8::MAX, u8::MAX],
233+
]
234+
.into_iter()
235+
.map(|arr| DropCounter::new(&drop_counter, arr))
236+
.enumerate()
237+
.map(swap)
238+
.collect()
239+
}
240+
241+
fn check_will_deallocate_unconsumed_iter_values(
242+
drop_counter: &Arc<AtomicUsize>,
243+
mut iter: impl Iterator + DoubleEndedIterator + ExactSizeIterator,
244+
) {
245+
assert_eq!(drop_counter.load(Ordering::Relaxed), 8);
246+
247+
assert_eq!(iter.len(), 8);
248+
249+
assert_eq!(drop_counter.load(Ordering::Relaxed), 8);
250+
251+
let _ = iter.next().unwrap();
252+
let _ = iter.next_back().unwrap();
253+
254+
assert_eq!(drop_counter.load(Ordering::Relaxed), 6);
255+
256+
drop(iter);
257+
258+
assert_eq!(drop_counter.load(Ordering::Relaxed), 0);
259+
}
260+
261+
#[test]
262+
fn into_iter_will_deallocate_unconsumed_iter_values() {
263+
let drop_counter = Arc::new(AtomicUsize::new(0));
264+
265+
let tree = setup_will_deallocate_unconsumed_iter_values(&drop_counter);
266+
267+
check_will_deallocate_unconsumed_iter_values(&drop_counter, tree.into_iter());
268+
}
269+
270+
#[test]
271+
fn into_keys_will_deallocate_unconsumed_iter_values() {
272+
let drop_counter = Arc::new(AtomicUsize::new(0));
273+
274+
let tree = setup_will_deallocate_unconsumed_iter_values(&drop_counter);
275+
276+
check_will_deallocate_unconsumed_iter_values(&drop_counter, tree.into_keys());
277+
}
278+
279+
#[test]
280+
fn into_values_will_deallocate_unconsumed_iter_values() {
281+
let drop_counter = Arc::new(AtomicUsize::new(0));
282+
283+
let tree = setup_will_deallocate_unconsumed_iter_values(&drop_counter);
284+
285+
check_will_deallocate_unconsumed_iter_values(&drop_counter, tree.into_values());
286+
}
287+
288+
#[test]
289+
fn empty_tree_empty_iterator() {
290+
let tree: TreeMap<u8, usize> = TreeMap::new();
291+
let mut it = tree.into_iter();
292+
assert_eq!(it.next(), None);
293+
}
294+
}

src/nodes/iterator.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ struct RawIteratorInner<K, V, const PREFIX_LEN: usize> {
99
end: LeafPtr<K, V, PREFIX_LEN>,
1010
}
1111

12+
impl<K, V, const PREFIX_LEN: usize> Copy for RawIteratorInner<K, V, PREFIX_LEN> {}
13+
14+
impl<K, V, const PREFIX_LEN: usize> Clone for RawIteratorInner<K, V, PREFIX_LEN> {
15+
fn clone(&self) -> Self {
16+
Self {
17+
start: self.start.clone(),
18+
end: self.end.clone(),
19+
}
20+
}
21+
}
22+
1223
impl<K, V, const PREFIX_LEN: usize> fmt::Debug for RawIteratorInner<K, V, PREFIX_LEN> {
1324
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1425
f.debug_struct("RawIteratorInner")
@@ -27,6 +38,16 @@ pub struct RawIterator<K, V, const PREFIX_LEN: usize> {
2738
state: Option<RawIteratorInner<K, V, PREFIX_LEN>>,
2839
}
2940

41+
impl<K, V, const PREFIX_LEN: usize> Copy for RawIterator<K, V, PREFIX_LEN> {}
42+
43+
impl<K, V, const PREFIX_LEN: usize> Clone for RawIterator<K, V, PREFIX_LEN> {
44+
fn clone(&self) -> Self {
45+
Self {
46+
state: self.state.clone(),
47+
}
48+
}
49+
}
50+
3051
impl<K, V, const PREFIX_LEN: usize> fmt::Debug for RawIterator<K, V, PREFIX_LEN> {
3152
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3253
f.debug_struct("RawIterator")

src/nodes/operations/deallocate.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::{ConcreteNodePtr, InnerNode, LeafNode, NodePtr, OpaqueNodePtr};
1+
use crate::{ConcreteNodePtr, InnerNode, LeafNode, NodePtr, OpaqueNodePtr, RawIterator};
22

3-
/// Deallocate all the leaf nodes in the linked list starting from `start`.
3+
/// Deallocate all the leaf nodes in the linked list starting that are within
4+
/// the given iterator.
45
///
56
/// # Safety
67
/// - This function must only be called once for this `start` node and all
@@ -9,19 +10,12 @@ use crate::{ConcreteNodePtr, InnerNode, LeafNode, NodePtr, OpaqueNodePtr};
910
/// - This function should not be called concurrently with any read of the
1011
/// tree, otherwise it could result in a use-after-free.
1112
pub unsafe fn deallocate_leaves<K, V, const PREFIX_LEN: usize>(
12-
start: NodePtr<PREFIX_LEN, LeafNode<K, V, PREFIX_LEN>>,
13+
mut leaf_range: RawIterator<K, V, PREFIX_LEN>,
1314
) {
14-
let mut current = start;
15-
16-
loop {
15+
// SAFETY: Covered by function safety doc
16+
while let Some(leaf_ptr) = unsafe { leaf_range.next() } {
1717
// SAFETY: Covered by function safety doc
18-
let leaf = unsafe { NodePtr::deallocate_node_ptr(current) };
19-
20-
if let Some(next) = leaf.next {
21-
current = next;
22-
} else {
23-
break;
24-
}
18+
let _ = unsafe { NodePtr::deallocate_node_ptr(leaf_ptr) };
2519
}
2620
}
2721

0 commit comments

Comments
 (0)