Skip to content

Commit d0bda90

Browse files
committed
fix unsoundness in Vec::extract_if
The implementation of the `Vec::extract_if` iterator invokes UB by always constructing a mutable slice for the entire length of the vector even though that span of memory can contain holes from items already drained. The safety contract of `slice::from_raw_parts` requires that all elements must be properly initialized. As an example we can look at the following code: ```rust let mut v = vec![Box::new(0u64), Box::new(1u64)]; for item in v.extract_if(.., |x| **x == 0) { drop(item); } ``` In the second iteration a `&mut [Box<u64>]` slice of length 2 will be constructed. The first slot of the slice contains the bitpattern of an already deallocated box, causing UB. This fixes the issue by only creating references to valid items and using pointer manipulation for the rest. I have also taken the liberty to remove the big `unsafe` blocks in place of targetted ones with a SAFETY comment. The approach closely mirrors the implementation of `Vec::retain_mut`. Signed-off-by: Petros Angelatos <[email protected]>
1 parent 7376077 commit d0bda90

File tree

1 file changed

+31
-25
lines changed

1 file changed

+31
-25
lines changed

library/alloc/src/vec/extract_if.rs

+31-25
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,29 @@ where
6464
type Item = T;
6565

6666
fn next(&mut self) -> Option<T> {
67-
unsafe {
68-
while self.idx < self.end {
69-
let i = self.idx;
70-
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
71-
let drained = (self.pred)(&mut v[i]);
72-
// Update the index *after* the predicate is called. If the index
73-
// is updated prior and the predicate panics, the element at this
74-
// index would be leaked.
75-
self.idx += 1;
76-
if drained {
77-
self.del += 1;
78-
return Some(ptr::read(&v[i]));
79-
} else if self.del > 0 {
80-
let del = self.del;
81-
let src: *const T = &v[i];
82-
let dst: *mut T = &mut v[i - del];
83-
ptr::copy_nonoverlapping(src, dst, 1);
67+
while self.idx < self.end {
68+
let i = self.idx;
69+
// SAFETY: Unchecked element must be valid.
70+
let cur = unsafe { &mut *self.vec.as_mut_ptr().add(i) };
71+
let drained = (self.pred)(cur);
72+
// Update the index *after* the predicate is called. If the index
73+
// is updated prior and the predicate panics, the element at this
74+
// index would be leaked.
75+
self.idx += 1;
76+
if drained {
77+
self.del += 1;
78+
// SAFETY: We never touch this element again after returning it.
79+
return Some(unsafe { ptr::read(cur) });
80+
} else if self.del > 0 {
81+
// SAFETY: `self.del` > 0, so the hole slot must not overlap with current element.
82+
// We use copy for move, and never touch this element again.
83+
unsafe {
84+
let hole_slot = self.vec.as_mut_ptr().add(i - self.del);
85+
ptr::copy_nonoverlapping(cur, hole_slot, 1);
8486
}
8587
}
86-
None
8788
}
89+
None
8890
}
8991

9092
fn size_hint(&self) -> (usize, Option<usize>) {
@@ -95,14 +97,18 @@ where
9597
#[stable(feature = "extract_if", since = "1.87.0")]
9698
impl<T, F, A: Allocator> Drop for ExtractIf<'_, T, F, A> {
9799
fn drop(&mut self) {
98-
unsafe {
99-
if self.idx < self.old_len && self.del > 0 {
100-
let ptr = self.vec.as_mut_ptr();
101-
let src = ptr.add(self.idx);
102-
let dst = src.sub(self.del);
103-
let tail_len = self.old_len - self.idx;
104-
src.copy_to(dst, tail_len);
100+
if self.del > 0 {
101+
// SAFETY: Trailing unchecked items must be valid since we never touch them.
102+
unsafe {
103+
ptr::copy(
104+
self.vec.as_ptr().add(self.idx),
105+
self.vec.as_mut_ptr().add(self.idx - self.del),
106+
self.old_len - self.idx,
107+
);
105108
}
109+
}
110+
// SAFETY: After filling holes, all items are in contiguous memory.
111+
unsafe {
106112
self.vec.set_len(self.old_len - self.del);
107113
}
108114
}

0 commit comments

Comments
 (0)