Skip to content

Commit aba7a07

Browse files
committed
Fix provenance UB and alignment UB
A &Header cannot be used to get a useful pointer to data beyond it, because the pointer from the as-cast of the &Header only has provenance over the Header. After a set_len call that decreases the length, it is invalid to create a slice then try to get_unchecked into the region between the old and new length, because the reference in the slice that the ThinVec now Derefs to does not have provenance over that region. Alternatively, this is UB because the docs stipulate that you're not allowed to use `get_unchecked` to index out of bounds. Misaligned data pointers were produced when the gecko-ffi feature was enabled and T has an alignment greater than 4. I think the use of align_offset in tests is subtly wrong, align_offset seems to be for optimizations only. The docs say that a valid implementation may always return usize::MAX.
1 parent 9705b3c commit aba7a07

File tree

1 file changed

+21
-26
lines changed

1 file changed

+21
-26
lines changed

src/lib.rs

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -264,23 +264,6 @@ impl Header {
264264
fn set_len(&mut self, len: usize) {
265265
self._len = assert_size(len);
266266
}
267-
268-
fn data<T>(&self) -> *mut T {
269-
let header_size = mem::size_of::<Header>();
270-
let padding = padding::<T>();
271-
272-
let ptr = self as *const Header as *mut Header as *mut u8;
273-
274-
unsafe {
275-
if padding > 0 && self.cap() == 0 {
276-
// The empty header isn't well-aligned, just make an aligned one up
277-
NonNull::dangling().as_ptr()
278-
} else {
279-
// This could technically result in overflow, but padding would have to be absurdly large for this to occur.
280-
ptr.add(header_size + padding) as *mut T
281-
}
282-
}
283-
}
284267
}
285268

286269
#[cfg(feature = "gecko-ffi")]
@@ -321,10 +304,10 @@ impl Header {
321304
/// optimize everything to not do that (basically, make ptr == len and branch
322305
/// on size == 0 in every method), but it's a bunch of work for something that
323306
/// doesn't matter much.
324-
#[cfg(any(not(feature = "gecko-ffi"), test))]
307+
#[cfg(any(not(feature = "gecko-ffi"), test, miri))]
325308
static EMPTY_HEADER: Header = Header { _len: 0, _cap: 0 };
326309

327-
#[cfg(all(feature = "gecko-ffi", not(test)))]
310+
#[cfg(all(feature = "gecko-ffi", not(test), not(miri)))]
328311
extern "C" {
329312
#[link_name = "sEmptyTArrayHeader"]
330313
static EMPTY_HEADER: Header;
@@ -470,7 +453,18 @@ impl<T> ThinVec<T> {
470453
unsafe { self.ptr.as_ref() }
471454
}
472455
fn data_raw(&self) -> *mut T {
473-
self.header().data()
456+
unsafe {
457+
if self.header().cap() == 0 {
458+
// The empty header isn't well-aligned, just make an aligned one up
459+
NonNull::dangling().as_ptr()
460+
} else {
461+
// This could technically result in overflow, but padding would have to be absurdly large for this to occur.
462+
let padding = padding::<T>();
463+
let header_size = mem::size_of::<Header>();
464+
let ptr = self.ptr.as_ptr() as *mut u8;
465+
ptr.add(header_size + padding) as *mut T
466+
}
467+
}
474468
}
475469

476470
// This is unsafe when the header is EMPTY_HEADER.
@@ -565,7 +559,7 @@ impl<T> ThinVec<T> {
565559
// doesn't re-drop the just-failed value.
566560
let new_len = self.len() - 1;
567561
self.set_len(new_len);
568-
ptr::drop_in_place(self.get_unchecked_mut(new_len));
562+
ptr::drop_in_place(self.data_raw().add(new_len));
569563
}
570564
}
571565
}
@@ -915,7 +909,7 @@ impl<T> ThinVec<T> {
915909
(*ptr).set_cap(new_cap);
916910
self.ptr = NonNull::new_unchecked(ptr);
917911
} else {
918-
let mut new_header = header_with_capacity::<T>(new_cap);
912+
let new_header = header_with_capacity::<T>(new_cap);
919913

920914
// If we get here and have a non-zero len, then we must be handling
921915
// a gecko auto array, and we have items in a stack buffer. We shouldn't
@@ -931,8 +925,9 @@ impl<T> ThinVec<T> {
931925
let len = self.len();
932926
if cfg!(feature = "gecko-ffi") && len > 0 {
933927
new_header
934-
.as_mut()
935-
.data::<T>()
928+
.as_ptr()
929+
.add(1)
930+
.cast::<T>()
936931
.copy_from_nonoverlapping(self.data_raw(), len);
937932
self.set_len(0);
938933
}
@@ -2654,9 +2649,9 @@ mod std_tests {
26542649
macro_rules! assert_aligned_head_ptr {
26552650
($typename:ty) => {{
26562651
let v: ThinVec<$typename> = ThinVec::with_capacity(1 /* ensure allocation */);
2657-
let head_ptr: *mut $typename = v.header().data::<$typename>();
2652+
let head_ptr: *mut $typename = v.data_raw();
26582653
assert_eq!(
2659-
head_ptr.align_offset(std::mem::align_of::<$typename>()),
2654+
head_ptr as usize % std::mem::align_of::<$typename>(),
26602655
0,
26612656
"expected Header::data<{}> to be aligned",
26622657
stringify!($typename)

0 commit comments

Comments
 (0)