Skip to content

Commit e3f7626

Browse files
authored
Rollup merge of #141032 - petrosagg:extract-if-ub, r=joboet
avoid violating `slice::from_raw_parts` safety contract in `Vec::extract_if` The implementation of the `Vec::extract_if` iterator violates the safety contract adverized by `slice::from_raw_parts` 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, which is invalid. 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`. **Note to reviewers:** The diff is easier to follow with whitespace hidden.
2 parents a028b7a + e9b2c4f commit e3f7626

File tree

2 files changed

+49
-25
lines changed

2 files changed

+49
-25
lines changed

library/alloc/src/vec/extract_if.rs

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,37 @@ 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:
70+
// We know that `i < self.end` from the if guard and that `self.end <= self.old_len` from
71+
// the validity of `Self`. Therefore `i` points to an element within `vec`.
72+
//
73+
// Additionally, the i-th element is valid because each element is visited at most once
74+
// and it is the first time we access vec[i].
75+
//
76+
// Note: we can't use `vec.get_unchecked_mut(i)` here since the precondition for that
77+
// function is that i < vec.len(), but we've set vec's length to zero.
78+
let cur = unsafe { &mut *self.vec.as_mut_ptr().add(i) };
79+
let drained = (self.pred)(cur);
80+
// Update the index *after* the predicate is called. If the index
81+
// is updated prior and the predicate panics, the element at this
82+
// index would be leaked.
83+
self.idx += 1;
84+
if drained {
85+
self.del += 1;
86+
// SAFETY: We never touch this element again after returning it.
87+
return Some(unsafe { ptr::read(cur) });
88+
} else if self.del > 0 {
89+
// SAFETY: `self.del` > 0, so the hole slot must not overlap with current element.
90+
// We use copy for move, and never touch this element again.
91+
unsafe {
92+
let hole_slot = self.vec.as_mut_ptr().add(i - self.del);
93+
ptr::copy_nonoverlapping(cur, hole_slot, 1);
8494
}
8595
}
86-
None
8796
}
97+
None
8898
}
8999

90100
fn size_hint(&self) -> (usize, Option<usize>) {
@@ -95,14 +105,18 @@ where
95105
#[stable(feature = "extract_if", since = "1.87.0")]
96106
impl<T, F, A: Allocator> Drop for ExtractIf<'_, T, F, A> {
97107
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);
108+
if self.del > 0 {
109+
// SAFETY: Trailing unchecked items must be valid since we never touch them.
110+
unsafe {
111+
ptr::copy(
112+
self.vec.as_ptr().add(self.idx),
113+
self.vec.as_mut_ptr().add(self.idx - self.del),
114+
self.old_len - self.idx,
115+
);
105116
}
117+
}
118+
// SAFETY: After filling holes, all items are in contiguous memory.
119+
unsafe {
106120
self.vec.set_len(self.old_len - self.del);
107121
}
108122
}

src/tools/miri/tests/pass/vec.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ fn miri_issue_2759() {
169169
input.replace_range(0..0, "0");
170170
}
171171

172+
/// This was skirting the edge of UB, let's make sure it remains on the sound side.
173+
/// Context: <https://github.com/rust-lang/rust/pull/141032>.
174+
fn extract_if() {
175+
let mut v = vec![Box::new(0u64), Box::new(1u64)];
176+
for item in v.extract_if(.., |x| **x == 0) {
177+
drop(item);
178+
}
179+
}
180+
172181
fn main() {
173182
assert_eq!(vec_reallocate().len(), 5);
174183

@@ -199,4 +208,5 @@ fn main() {
199208
swap_remove();
200209
reverse();
201210
miri_issue_2759();
211+
extract_if();
202212
}

0 commit comments

Comments
 (0)