Skip to content

Commit 61fadf8

Browse files
authored
Rollup merge of rust-lang#106084 - RalfJung:into-iter, r=thomcc
fix vec::IntoIter::drop on high-alignment ZST This fixes a soundness bug: IntoIter would call `drop_in_place` on an insufficiently aligned pointer. So if a ZST with alignment greater 1 had drop glue, that would be called with an unaligned reference. Since rust-lang#103957, Miri checks alignment even if the type does not have drop glue, which is how this bug was found. r? ``@thomcc``
2 parents ae61070 + a48d2e1 commit 61fadf8

File tree

3 files changed

+38
-18
lines changed

3 files changed

+38
-18
lines changed

Diff for: library/alloc/src/vec/into_iter.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ pub struct IntoIter<
4040
// to avoid dropping the allocator twice we need to wrap it into ManuallyDrop
4141
pub(super) alloc: ManuallyDrop<A>,
4242
pub(super) ptr: *const T,
43-
pub(super) end: *const T,
43+
pub(super) end: *const T, // If T is a ZST, this is actually ptr+len. This encoding is picked so that
44+
// ptr == end is a quick test for the Iterator being empty, that works
45+
// for both ZST and non-ZST.
4446
}
4547

4648
#[stable(feature = "vec_intoiter_debug", since = "1.13.0")]
@@ -132,7 +134,9 @@ impl<T, A: Allocator> IntoIter<T, A> {
132134

133135
/// Forgets to Drop the remaining elements while still allowing the backing allocation to be freed.
134136
pub(crate) fn forget_remaining_elements(&mut self) {
135-
self.ptr = self.end;
137+
// For th ZST case, it is crucial that we mutate `end` here, not `ptr`.
138+
// `ptr` must stay aligned, while `end` may be unaligned.
139+
self.end = self.ptr;
136140
}
137141

138142
#[cfg(not(no_global_oom_handling))]
@@ -184,10 +188,9 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
184188
if self.ptr == self.end {
185189
None
186190
} else if T::IS_ZST {
187-
// purposefully don't use 'ptr.offset' because for
188-
// vectors with 0-size elements this would return the
189-
// same pointer.
190-
self.ptr = self.ptr.wrapping_byte_add(1);
191+
// `ptr` has to stay where it is to remain aligned, so we reduce the length by 1 by
192+
// reducing the `end`.
193+
self.end = self.end.wrapping_byte_sub(1);
191194

192195
// Make up a value of this ZST.
193196
Some(unsafe { mem::zeroed() })
@@ -214,10 +217,8 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
214217
let step_size = self.len().min(n);
215218
let to_drop = ptr::slice_from_raw_parts_mut(self.ptr as *mut T, step_size);
216219
if T::IS_ZST {
217-
// SAFETY: due to unchecked casts of unsigned amounts to signed offsets the wraparound
218-
// effectively results in unsigned pointers representing positions 0..usize::MAX,
219-
// which is valid for ZSTs.
220-
self.ptr = self.ptr.wrapping_byte_add(step_size);
220+
// See `next` for why we sub `end` here.
221+
self.end = self.end.wrapping_byte_sub(step_size);
221222
} else {
222223
// SAFETY: the min() above ensures that step_size is in bounds
223224
self.ptr = unsafe { self.ptr.add(step_size) };
@@ -250,7 +251,7 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
250251
return Err(unsafe { array::IntoIter::new_unchecked(raw_ary, 0..len) });
251252
}
252253

253-
self.ptr = self.ptr.wrapping_byte_add(N);
254+
self.end = self.end.wrapping_byte_sub(N);
254255
// Safety: ditto
255256
return Ok(unsafe { raw_ary.transpose().assume_init() });
256257
}

Diff for: src/tools/miri/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ Definite bugs found:
639639
* [Data race in `thread::scope`](https://github.com/rust-lang/rust/issues/98498)
640640
* [`regex` incorrectly handling unaligned `Vec<u8>` buffers](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3)
641641
* [Incorrect use of `compare_exchange_weak` in `once_cell`](https://github.com/matklad/once_cell/issues/186)
642+
* [Dropping with unaligned pointers in `vec::IntoIter`](https://github.com/rust-lang/rust/pull/106084)
642643

643644
Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):
644645

Diff for: src/tools/miri/tests/pass/vec.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//@compile-flags: -Zmiri-strict-provenance
2+
#![feature(iter_advance_by, iter_next_chunk)]
3+
24
// Gather all references from a mutable iterator and make sure Miri notices if
35
// using them is dangerous.
46
fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator<Item = &'a mut T>) {
@@ -37,15 +39,31 @@ fn vec_into_iter() -> u8 {
3739
}
3840

3941
fn vec_into_iter_rev() -> u8 {
40-
vec![1, 2, 3, 4].into_iter().map(|x| x * x).fold(0, |x, y| x + y)
42+
vec![1, 2, 3, 4].into_iter().rev().map(|x| x * x).fold(0, |x, y| x + y)
4143
}
4244

43-
fn vec_into_iter_zst() -> usize {
44-
vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum()
45+
fn vec_into_iter_zst() {
46+
for _ in vec![[0u64; 0]].into_iter() {}
47+
let v = vec![[0u64; 0], [0u64; 0]].into_iter().map(|x| x.len()).sum::<usize>();
48+
assert_eq!(v, 0);
49+
50+
let mut it = vec![[0u64; 0], [0u64; 0]].into_iter();
51+
it.advance_by(1);
52+
drop(it);
53+
54+
let mut it = vec![[0u64; 0], [0u64; 0]].into_iter();
55+
it.next_chunk::<1>().unwrap();
56+
drop(it);
57+
58+
let mut it = vec![[0u64; 0], [0u64; 0]].into_iter();
59+
it.next_chunk::<4>().unwrap_err();
60+
drop(it);
4561
}
4662

47-
fn vec_into_iter_rev_zst() -> usize {
48-
vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum()
63+
fn vec_into_iter_rev_zst() {
64+
for _ in vec![[0u64; 0]; 5].into_iter().rev() {}
65+
let v = vec![[0u64; 0], [0u64; 0]].into_iter().rev().map(|x| x.len()).sum::<usize>();
66+
assert_eq!(v, 0);
4967
}
5068

5169
fn vec_iter_and_mut() {
@@ -150,8 +168,8 @@ fn main() {
150168
assert_eq!(vec_into_iter(), 30);
151169
assert_eq!(vec_into_iter_rev(), 30);
152170
vec_iter_and_mut();
153-
assert_eq!(vec_into_iter_zst(), 0);
154-
assert_eq!(vec_into_iter_rev_zst(), 0);
171+
vec_into_iter_zst();
172+
vec_into_iter_rev_zst();
155173
vec_iter_and_mut_rev();
156174

157175
assert_eq!(make_vec().capacity(), 4);

0 commit comments

Comments
 (0)