Skip to content

Commit f97769a

Browse files
committed
Auto merge of #84603 - cuviper:beta-next, r=Mark-Simulacrum
[beta] backports This backports two beta-accepted PRs, fixing CVE-2020-36323 and CVE-2021-31162. - Fixes API soundness issue in `join()` #81728 - Fix double-drop in `Vec::from_iter(vec.into_iter())` specialization when items drop during panic #83629
2 parents cc6d66f + 836fef0 commit f97769a

File tree

5 files changed

+112
-31
lines changed

5 files changed

+112
-31
lines changed

library/alloc/src/str.rs

+25-19
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ impl<S: Borrow<str>> Join<&str> for [S] {
9090
}
9191
}
9292

93-
macro_rules! spezialize_for_lengths {
94-
($separator:expr, $target:expr, $iter:expr; $($num:expr),*) => {
93+
macro_rules! specialize_for_lengths {
94+
($separator:expr, $target:expr, $iter:expr; $($num:expr),*) => {{
9595
let mut target = $target;
9696
let iter = $iter;
9797
let sep_bytes = $separator;
@@ -102,19 +102,22 @@ macro_rules! spezialize_for_lengths {
102102
$num => {
103103
for s in iter {
104104
copy_slice_and_advance!(target, sep_bytes);
105-
copy_slice_and_advance!(target, s.borrow().as_ref());
105+
let content_bytes = s.borrow().as_ref();
106+
copy_slice_and_advance!(target, content_bytes);
106107
}
107108
},
108109
)*
109110
_ => {
110111
// arbitrary non-zero size fallback
111112
for s in iter {
112113
copy_slice_and_advance!(target, sep_bytes);
113-
copy_slice_and_advance!(target, s.borrow().as_ref());
114+
let content_bytes = s.borrow().as_ref();
115+
copy_slice_and_advance!(target, content_bytes);
114116
}
115117
}
116118
}
117-
};
119+
target
120+
}}
118121
}
119122

120123
macro_rules! copy_slice_and_advance {
@@ -153,30 +156,33 @@ where
153156
// if the `len` calculation overflows, we'll panic
154157
// we would have run out of memory anyway and the rest of the function requires
155158
// the entire Vec pre-allocated for safety
156-
let len = sep_len
159+
let reserved_len = sep_len
157160
.checked_mul(iter.len())
158161
.and_then(|n| {
159162
slice.iter().map(|s| s.borrow().as_ref().len()).try_fold(n, usize::checked_add)
160163
})
161164
.expect("attempt to join into collection with len > usize::MAX");
162165

163-
// crucial for safety
164-
let mut result = Vec::with_capacity(len);
165-
assert!(result.capacity() >= len);
166+
// prepare an uninitialized buffer
167+
let mut result = Vec::with_capacity(reserved_len);
168+
debug_assert!(result.capacity() >= reserved_len);
166169

167170
result.extend_from_slice(first.borrow().as_ref());
168171

169172
unsafe {
170-
{
171-
let pos = result.len();
172-
let target = result.get_unchecked_mut(pos..len);
173-
174-
// copy separator and slices over without bounds checks
175-
// generate loops with hardcoded offsets for small separators
176-
// massive improvements possible (~ x2)
177-
spezialize_for_lengths!(sep, target, iter; 0, 1, 2, 3, 4);
178-
}
179-
result.set_len(len);
173+
let pos = result.len();
174+
let target = result.get_unchecked_mut(pos..reserved_len);
175+
176+
// copy separator and slices over without bounds checks
177+
// generate loops with hardcoded offsets for small separators
178+
// massive improvements possible (~ x2)
179+
let remain = specialize_for_lengths!(sep, target, iter; 0, 1, 2, 3, 4);
180+
181+
// A weird borrow implementation may return different
182+
// slices for the length calculation and the actual copy.
183+
// Make sure we don't expose uninitialized bytes to the caller.
184+
let result_len = reserved_len - remain.len();
185+
result.set_len(result_len);
180186
}
181187
result
182188
}

library/alloc/src/vec/into_iter.rs

+18-9
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,29 @@ impl<T, A: Allocator> IntoIter<T, A> {
8585
ptr::slice_from_raw_parts_mut(self.ptr as *mut T, self.len())
8686
}
8787

88-
pub(super) fn drop_remaining(&mut self) {
89-
unsafe {
90-
ptr::drop_in_place(self.as_mut_slice());
91-
}
92-
self.ptr = self.end;
93-
}
88+
/// Drops remaining elements and relinquishes the backing allocation.
89+
///
90+
/// This is roughly equivalent to the following, but more efficient
91+
///
92+
/// ```
93+
/// # let mut into_iter = Vec::<u8>::with_capacity(10).into_iter();
94+
/// (&mut into_iter).for_each(core::mem::drop);
95+
/// unsafe { core::ptr::write(&mut into_iter, Vec::new().into_iter()); }
96+
/// ```
97+
pub(super) fn forget_allocation_drop_remaining(&mut self) {
98+
let remaining = self.as_raw_mut_slice();
9499

95-
/// Relinquishes the backing allocation, equivalent to
96-
/// `ptr::write(&mut self, Vec::new().into_iter())`
97-
pub(super) fn forget_allocation(&mut self) {
100+
// overwrite the individual fields instead of creating a new
101+
// struct and then overwriting &mut self.
102+
// this creates less assembly
98103
self.cap = 0;
99104
self.buf = unsafe { NonNull::new_unchecked(RawVec::NEW.ptr()) };
100105
self.ptr = self.buf.as_ptr();
101106
self.end = self.buf.as_ptr();
107+
108+
unsafe {
109+
ptr::drop_in_place(remaining);
110+
}
102111
}
103112
}
104113

library/alloc/src/vec/source_iter_marker.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ where
7878
}
7979

8080
// drop any remaining values at the tail of the source
81-
src.drop_remaining();
8281
// but prevent drop of the allocation itself once IntoIter goes out of scope
83-
src.forget_allocation();
82+
// if the drop panics then we also leak any elements collected into dst_buf
83+
src.forget_allocation_drop_remaining();
8484

8585
let vec = unsafe {
8686
let len = dst.offset_from(dst_buf) as usize;

library/alloc/tests/str.rs

+30
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,36 @@ fn test_join_for_different_lengths_with_long_separator() {
160160
test_join!("~~~~~a~~~~~bc", ["", "a", "bc"], "~~~~~");
161161
}
162162

163+
#[test]
164+
fn test_join_isue_80335() {
165+
use core::{borrow::Borrow, cell::Cell};
166+
167+
struct WeirdBorrow {
168+
state: Cell<bool>,
169+
}
170+
171+
impl Default for WeirdBorrow {
172+
fn default() -> Self {
173+
WeirdBorrow { state: Cell::new(false) }
174+
}
175+
}
176+
177+
impl Borrow<str> for WeirdBorrow {
178+
fn borrow(&self) -> &str {
179+
let state = self.state.get();
180+
if state {
181+
"0"
182+
} else {
183+
self.state.set(true);
184+
"123456"
185+
}
186+
}
187+
}
188+
189+
let arr: [WeirdBorrow; 3] = Default::default();
190+
test_join!("0-0-0", arr, "-");
191+
}
192+
163193
#[test]
164194
#[cfg_attr(miri, ignore)] // Miri is too slow
165195
fn test_unsafe_slice() {

library/alloc/tests/vec.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,7 @@ fn test_from_iter_specialization_head_tail_drop() {
10271027
}
10281028

10291029
#[test]
1030-
fn test_from_iter_specialization_panic_drop() {
1030+
fn test_from_iter_specialization_panic_during_iteration_drops() {
10311031
let drop_count: Vec<_> = (0..=2).map(|_| Rc::new(())).collect();
10321032
let src: Vec<_> = drop_count.iter().cloned().collect();
10331033
let iter = src.into_iter();
@@ -1050,6 +1050,42 @@ fn test_from_iter_specialization_panic_drop() {
10501050
);
10511051
}
10521052

1053+
#[test]
1054+
fn test_from_iter_specialization_panic_during_drop_leaks() {
1055+
static mut DROP_COUNTER: usize = 0;
1056+
1057+
#[derive(Debug)]
1058+
enum Droppable {
1059+
DroppedTwice(Box<i32>),
1060+
PanicOnDrop,
1061+
}
1062+
1063+
impl Drop for Droppable {
1064+
fn drop(&mut self) {
1065+
match self {
1066+
Droppable::DroppedTwice(_) => {
1067+
unsafe {
1068+
DROP_COUNTER += 1;
1069+
}
1070+
println!("Dropping!")
1071+
}
1072+
Droppable::PanicOnDrop => {
1073+
if !std::thread::panicking() {
1074+
panic!();
1075+
}
1076+
}
1077+
}
1078+
}
1079+
}
1080+
1081+
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
1082+
let v = vec![Droppable::DroppedTwice(Box::new(123)), Droppable::PanicOnDrop];
1083+
let _ = v.into_iter().take(0).collect::<Vec<_>>();
1084+
}));
1085+
1086+
assert_eq!(unsafe { DROP_COUNTER }, 1);
1087+
}
1088+
10531089
#[test]
10541090
fn test_cow_from() {
10551091
let borrowed: &[_] = &["borrowed", "(slice)"];

0 commit comments

Comments
 (0)