Skip to content

Commit 819866c

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 414482f commit 819866c

File tree

1 file changed

+30
-25
lines changed

1 file changed

+30
-25
lines changed

library/alloc/src/vec/extract_if.rs

+30-25
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,28 @@ 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+
// SAFETY: Unchecked element must be valid.
69+
let cur = unsafe { &mut *self.vec.as_mut_ptr().add(self.idx) };
70+
let drained = (self.pred)(cur);
71+
// Update the index *after* the predicate is called. If the index
72+
// is updated prior and the predicate panics, the element at this
73+
// index would be leaked.
74+
self.idx += 1;
75+
if drained {
76+
self.del += 1;
77+
// SAFETY: We never touch this element again after returning it.
78+
return Some(unsafe { ptr::read(cur) });
79+
} else if self.del > 0 {
80+
// SAFETY: `self.del` > 0, so the hole slot must not overlap with current element.
81+
// We use copy for move, and never touch this element again.
82+
unsafe {
83+
let hole_slot = self.vec.as_mut_ptr().add(self.idx - self.del);
84+
ptr::copy_nonoverlapping(cur, hole_slot, 1);
8485
}
8586
}
86-
None
8787
}
88+
None
8889
}
8990

9091
fn size_hint(&self) -> (usize, Option<usize>) {
@@ -95,14 +96,18 @@ where
9596
#[stable(feature = "extract_if", since = "1.87.0")]
9697
impl<T, F, A: Allocator> Drop for ExtractIf<'_, T, F, A> {
9798
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);
99+
if self.del > 0 {
100+
// SAFETY: Trailing unchecked items must be valid since we never touch them.
101+
unsafe {
102+
ptr::copy(
103+
self.vec.as_ptr().add(self.idx),
104+
self.vec.as_mut_ptr().add(self.idx - self.del),
105+
self.old_len - self.idx,
106+
);
105107
}
108+
}
109+
// SAFETY: After filling holes, all items are in contiguous memory.
110+
unsafe {
106111
self.vec.set_len(self.old_len - self.del);
107112
}
108113
}

0 commit comments

Comments
 (0)