Skip to content

Commit 2acd80c

Browse files
authored
Rollup merge of #145067 - btj:patch-3, r=tgross35
RawVecInner: add missing `unsafe` to unsafe fns Some (module-private) functions in `library/alloc/src/raw_vec/mod.rs` are unsafe (i.e. may cause UB when called from safe code) but are not marked `unsafe`. Specifically: - `RawVecInner::grow_exact` causes UB if called with `len` and `additional` arguments such that `len + additional` is less than the current capacity. Indeed, in that case it calls [Allocator::grow](https://doc.rust-lang.org/std/alloc/trait.Allocator.html#method.grow) with a `new_layout` that is smaller than `old_layout`, which violates a safety precondition. - The RawVecInner methods for resizing the buffer cause UB if called with an `elem_layout` different from the one used to initially allocate the buffer, because in that case `Allocator::grow` or `Allocator::shrink` are called with an `old_layout` that does not *fit* the allocated block, which violates a safety precondition. - `RawVecInner::current_memory` might cause UB if called with an `elem_layout` different from the one used to initially allocate the buffer, because the `unchecked_mul` might overflow. - Furthermore, these methods cause UB if called with an `elem_layout` where the size is not a multiple of the alignment. This is because `Layout::repeat` is used (in `layout_array`) to compute the allocation's layout when allocating, which includes padding to ensure alignment of array elements, but simple multiplication is used (in `current_memory`) to compute the old allocation's layout when resizing or deallocating, which would cause the layout used to resize or deallocate to not *fit* the allocated block, which violates a safety precondition. I discovered these issues while performing formal verification of `library/alloc/src/raw_vec/mod.rs` per [Challenge 19](https://model-checking.github.io/verify-rust-std/challenges/0019-rawvec.html) of the [AWS Rust Standard Library Verification Contest](https://aws.amazon.com/blogs/opensource/verify-the-safety-of-the-rust-standard-library/).
2 parents bbcbc78 + 96f385b commit 2acd80c

2 files changed

Lines changed: 123 additions & 36 deletions

File tree

‎library/alloc/src/raw_vec/mod.rs‎

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ impl<T, A: Allocator> RawVec<T, A> {
177177
/// the returned `RawVec`.
178178
#[inline]
179179
pub(crate)constfnnew_in(alloc:A) -> Self{
180+
// Check assumption made in `current_memory`
181+
const{assert!(T::LAYOUT.size() % T::LAYOUT.align() == 0)};
180182
Self{inner:RawVecInner::new_in(alloc,Alignment::of::<T>()),_marker:PhantomData}
181183
}
182184

@@ -328,7 +330,8 @@ impl<T, A: Allocator> RawVec<T, A> {
328330
#[inline]
329331
#[track_caller]
330332
pub(crate)fnreserve(&mutself,len:usize,additional:usize){
331-
self.inner.reserve(len, additional,T::LAYOUT)
333+
// SAFETY: All calls on self.inner pass T::LAYOUT as the elem_layout
334+
unsafe{self.inner.reserve(len, additional,T::LAYOUT)}
332335
}
333336

334337
/// A specialized version of `self.reserve(len, 1)` which requires the
@@ -337,7 +340,8 @@ impl<T, A: Allocator> RawVec<T, A> {
337340
#[inline(never)]
338341
#[track_caller]
339342
pub(crate)fngrow_one(&mutself){
340-
self.inner.grow_one(T::LAYOUT)
343+
// SAFETY: All calls on self.inner pass T::LAYOUT as the elem_layout
344+
unsafe{self.inner.grow_one(T::LAYOUT)}
341345
}
342346

343347
/// The same as `reserve`, but returns on errors instead of panicking or aborting.
@@ -346,7 +350,8 @@ impl<T, A: Allocator> RawVec<T, A> {
346350
len:usize,
347351
additional:usize,
348352
) -> Result<(),TryReserveError>{
349-
self.inner.try_reserve(len, additional,T::LAYOUT)
353+
// SAFETY: All calls on self.inner pass T::LAYOUT as the elem_layout
354+
unsafe{self.inner.try_reserve(len, additional,T::LAYOUT)}
350355
}
351356

352357
/// Ensures that the buffer contains at least enough space to hold `len +
@@ -369,7 +374,8 @@ impl<T, A: Allocator> RawVec<T, A> {
369374
#[cfg(not(no_global_oom_handling))]
370375
#[track_caller]
371376
pub(crate)fnreserve_exact(&mutself,len:usize,additional:usize){
372-
self.inner.reserve_exact(len, additional,T::LAYOUT)
377+
// SAFETY: All calls on self.inner pass T::LAYOUT as the elem_layout
378+
unsafe{self.inner.reserve_exact(len, additional,T::LAYOUT)}
373379
}
374380

375381
/// The same as `reserve_exact`, but returns on errors instead of panicking or aborting.
@@ -378,7 +384,8 @@ impl<T, A: Allocator> RawVec<T, A> {
378384
len:usize,
379385
additional:usize,
380386
) -> Result<(),TryReserveError>{
381-
self.inner.try_reserve_exact(len, additional,T::LAYOUT)
387+
// SAFETY: All calls on self.inner pass T::LAYOUT as the elem_layout
388+
unsafe{self.inner.try_reserve_exact(len, additional,T::LAYOUT)}
382389
}
383390

384391
/// Shrinks the buffer down to the specified capacity. If the given amount
@@ -395,7 +402,8 @@ impl<T, A: Allocator> RawVec<T, A> {
395402
#[track_caller]
396403
#[inline]
397404
pub(crate)fnshrink_to_fit(&mutself,cap:usize){
398-
self.inner.shrink_to_fit(cap,T::LAYOUT)
405+
// SAFETY: All calls on self.inner pass T::LAYOUT as the elem_layout
406+
unsafe{self.inner.shrink_to_fit(cap,T::LAYOUT)}
399407
}
400408
}
401409

@@ -518,8 +526,12 @@ impl<A: Allocator> RawVecInner<A> {
518526
&self.alloc
519527
}
520528

529+
/// # Safety
530+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
531+
/// initially construct `self`
532+
/// - `elem_layout`'s size must be a multiple of its alignment
521533
#[inline]
522-
fncurrent_memory(&self,elem_layout:Layout) -> Option<(NonNull<u8>,Layout)>{
534+
unsafefncurrent_memory(&self,elem_layout:Layout) -> Option<(NonNull<u8>,Layout)>{
523535
if elem_layout.size() == 0 || self.cap.as_inner() == 0{
524536
None
525537
}else{
@@ -535,48 +547,67 @@ impl<A: Allocator> RawVecInner<A> {
535547
}
536548
}
537549

550+
/// # Safety
551+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
552+
/// initially construct `self`
553+
/// - `elem_layout`'s size must be a multiple of its alignment
538554
#[cfg(not(no_global_oom_handling))]
539555
#[inline]
540556
#[track_caller]
541-
fnreserve(&mutself,len:usize,additional:usize,elem_layout:Layout){
557+
unsafefnreserve(&mutself,len:usize,additional:usize,elem_layout:Layout){
542558
// Callers expect this function to be very cheap when there is already sufficient capacity.
543559
// Therefore, we move all the resizing and error-handling logic from grow_amortized and
544560
// handle_reserve behind a call, while making sure that this function is likely to be
545561
// inlined as just a comparison and a call if the comparison fails.
546562
#[cold]
547-
fndo_reserve_and_handle<A:Allocator>(
563+
unsafefndo_reserve_and_handle<A:Allocator>(
548564
slf:&mutRawVecInner<A>,
549565
len:usize,
550566
additional:usize,
551567
elem_layout:Layout,
552568
){
553-
ifletErr(err) = slf.grow_amortized(len, additional, elem_layout){
569+
// SAFETY: Precondition passed to caller
570+
ifletErr(err) = unsafe{ slf.grow_amortized(len, additional, elem_layout)}{
554571
handle_error(err);
555572
}
556573
}
557574

558575
ifself.needs_to_grow(len, additional, elem_layout){
559-
do_reserve_and_handle(self, len, additional, elem_layout);
576+
unsafe{
577+
do_reserve_and_handle(self, len, additional, elem_layout);
578+
}
560579
}
561580
}
562581

582+
/// # Safety
583+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
584+
/// initially construct `self`
585+
/// - `elem_layout`'s size must be a multiple of its alignment
563586
#[cfg(not(no_global_oom_handling))]
564587
#[inline]
565588
#[track_caller]
566-
fngrow_one(&mutself,elem_layout:Layout){
567-
ifletErr(err) = self.grow_amortized(self.cap.as_inner(),1, elem_layout){
589+
unsafefngrow_one(&mutself,elem_layout:Layout){
590+
// SAFETY: Precondition passed to caller
591+
ifletErr(err) = unsafe{self.grow_amortized(self.cap.as_inner(),1, elem_layout)}{
568592
handle_error(err);
569593
}
570594
}
571595

572-
fntry_reserve(
596+
/// # Safety
597+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
598+
/// initially construct `self`
599+
/// - `elem_layout`'s size must be a multiple of its alignment
600+
unsafefntry_reserve(
573601
&mutself,
574602
len:usize,
575603
additional:usize,
576604
elem_layout:Layout,
577605
) -> Result<(),TryReserveError>{
578606
ifself.needs_to_grow(len, additional, elem_layout){
579-
self.grow_amortized(len, additional, elem_layout)?;
607+
// SAFETY: Precondition passed to caller
608+
unsafe{
609+
self.grow_amortized(len, additional, elem_layout)?;
610+
}
580611
}
581612
unsafe{
582613
// Inform the optimizer that the reservation has succeeded or wasn't needed
@@ -585,22 +616,34 @@ impl<A: Allocator> RawVecInner<A> {
585616
Ok(())
586617
}
587618

619+
/// # Safety
620+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
621+
/// initially construct `self`
622+
/// - `elem_layout`'s size must be a multiple of its alignment
588623
#[cfg(not(no_global_oom_handling))]
589624
#[track_caller]
590-
fnreserve_exact(&mutself,len:usize,additional:usize,elem_layout:Layout){
591-
ifletErr(err) = self.try_reserve_exact(len, additional, elem_layout){
625+
unsafefnreserve_exact(&mutself,len:usize,additional:usize,elem_layout:Layout){
626+
// SAFETY: Precondition passed to caller
627+
ifletErr(err) = unsafe{self.try_reserve_exact(len, additional, elem_layout)}{
592628
handle_error(err);
593629
}
594630
}
595631

596-
fntry_reserve_exact(
632+
/// # Safety
633+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
634+
/// initially construct `self`
635+
/// - `elem_layout`'s size must be a multiple of its alignment
636+
unsafefntry_reserve_exact(
597637
&mutself,
598638
len:usize,
599639
additional:usize,
600640
elem_layout:Layout,
601641
) -> Result<(),TryReserveError>{
602642
ifself.needs_to_grow(len, additional, elem_layout){
603-
self.grow_exact(len, additional, elem_layout)?;
643+
// SAFETY: Precondition passed to caller
644+
unsafe{
645+
self.grow_exact(len, additional, elem_layout)?;
646+
}
604647
}
605648
unsafe{
606649
// Inform the optimizer that the reservation has succeeded or wasn't needed
@@ -609,11 +652,16 @@ impl<A: Allocator> RawVecInner<A> {
609652
Ok(())
610653
}
611654

655+
/// # Safety
656+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
657+
/// initially construct `self`
658+
/// - `elem_layout`'s size must be a multiple of its alignment
659+
/// - `cap` must be less than or equal to `self.capacity(elem_layout.size())`
612660
#[cfg(not(no_global_oom_handling))]
613661
#[inline]
614662
#[track_caller]
615-
fnshrink_to_fit(&mutself,cap:usize,elem_layout:Layout){
616-
ifletErr(err) = self.shrink(cap, elem_layout){
663+
unsafefnshrink_to_fit(&mutself,cap:usize,elem_layout:Layout){
664+
ifletErr(err) = unsafe{self.shrink(cap, elem_layout)}{
617665
handle_error(err);
618666
}
619667
}
@@ -632,7 +680,13 @@ impl<A: Allocator> RawVecInner<A> {
632680
self.cap = unsafe{Cap::new_unchecked(cap)};
633681
}
634682

635-
fngrow_amortized(
683+
/// # Safety
684+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
685+
/// initially construct `self`
686+
/// - `elem_layout`'s size must be a multiple of its alignment
687+
/// - The sum of `len` and `additional` must be greater than or equal to
688+
/// `self.capacity(elem_layout.size())`
689+
unsafefngrow_amortized(
636690
&mutself,
637691
len:usize,
638692
additional:usize,
@@ -657,14 +711,25 @@ impl<A: Allocator> RawVecInner<A> {
657711

658712
let new_layout = layout_array(cap, elem_layout)?;
659713

660-
let ptr = finish_grow(new_layout,self.current_memory(elem_layout),&mutself.alloc)?;
661-
// SAFETY: layout_array would have resulted in a capacity overflow if we tried to allocate more than `isize::MAX` items
714+
// SAFETY:
715+
// - For the `current_memory` call: Precondition passed to caller
716+
// - For the `finish_grow` call: Precondition passed to caller
717+
// + `current_memory` does the right thing
718+
let ptr =
719+
unsafe{finish_grow(new_layout,self.current_memory(elem_layout),&mutself.alloc)? };
662720

721+
// SAFETY: layout_array would have resulted in a capacity overflow if we tried to allocate more than `isize::MAX` items
663722
unsafe{self.set_ptr_and_cap(ptr, cap)};
664723
Ok(())
665724
}
666725

667-
fngrow_exact(
726+
/// # Safety
727+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
728+
/// initially construct `self`
729+
/// - `elem_layout`'s size must be a multiple of its alignment
730+
/// - The sum of `len` and `additional` must be greater than or equal to
731+
/// `self.capacity(elem_layout.size())`
732+
unsafefngrow_exact(
668733
&mutself,
669734
len:usize,
670735
additional:usize,
@@ -679,17 +744,27 @@ impl<A: Allocator> RawVecInner<A> {
679744
let cap = len.checked_add(additional).ok_or(CapacityOverflow)?;
680745
let new_layout = layout_array(cap, elem_layout)?;
681746

682-
let ptr = finish_grow(new_layout,self.current_memory(elem_layout),&mutself.alloc)?;
747+
// SAFETY:
748+
// - For the `current_memory` call: Precondition passed to caller
749+
// - For the `finish_grow` call: Precondition passed to caller
750+
// + `current_memory` does the right thing
751+
let ptr =
752+
unsafe{finish_grow(new_layout,self.current_memory(elem_layout),&mutself.alloc)? };
683753
// SAFETY: layout_array would have resulted in a capacity overflow if we tried to allocate more than `isize::MAX` items
684754
unsafe{
685755
self.set_ptr_and_cap(ptr, cap);
686756
}
687757
Ok(())
688758
}
689759

760+
/// # Safety
761+
/// - `elem_layout` must be valid for `self`, i.e. it must be the same `elem_layout` used to
762+
/// initially construct `self`
763+
/// - `elem_layout`'s size must be a multiple of its alignment
764+
/// - `cap` must be less than or equal to `self.capacity(elem_layout.size())`
690765
#[cfg(not(no_global_oom_handling))]
691766
#[inline]
692-
fnshrink(&mutself,cap:usize,elem_layout:Layout) -> Result<(),TryReserveError>{
767+
unsafefnshrink(&mutself,cap:usize,elem_layout:Layout) -> Result<(),TryReserveError>{
693768
assert!(cap <= self.capacity(elem_layout.size()),"Tried to shrink to a larger capacity");
694769
// SAFETY: Just checked this isn't trying to grow
695770
unsafe{self.shrink_unchecked(cap, elem_layout)}
@@ -711,8 +786,12 @@ impl<A: Allocator> RawVecInner<A> {
711786
cap:usize,
712787
elem_layout:Layout,
713788
) -> Result<(),TryReserveError>{
714-
let(ptr, layout) =
715-
ifletSome(mem) = self.current_memory(elem_layout){ mem }else{returnOk(())};
789+
// SAFETY: Precondition passed to caller
790+
let(ptr, layout) = ifletSome(mem) = unsafe{self.current_memory(elem_layout)}{
791+
mem
792+
}else{
793+
returnOk(());
794+
};
716795

717796
// If shrinking to 0, deallocate the buffer. We don't reach this point
718797
// for the T::IS_ZST case since current_memory() will have returned
@@ -748,18 +827,26 @@ impl<A: Allocator> RawVecInner<A> {
748827
/// Ideally this function would take `self` by move, but it cannot because it exists to be
749828
/// called from a `Drop` impl.
750829
unsafefndeallocate(&mutself,elem_layout:Layout){
751-
ifletSome((ptr, layout)) = self.current_memory(elem_layout){
830+
// SAFETY: Precondition passed to caller
831+
ifletSome((ptr, layout)) = unsafe{self.current_memory(elem_layout)}{
752832
unsafe{
753833
self.alloc.deallocate(ptr, layout);
754834
}
755835
}
756836
}
757837
}
758838

839+
/// # Safety
840+
/// If `current_memory` matches `Some((ptr, old_layout))`:
841+
/// - `ptr` must denote a block of memory *currently allocated* via `alloc`
842+
/// - `old_layout` must *fit* that block of memory
843+
/// - `new_layout` must have the same alignment as `old_layout`
844+
/// - `new_layout.size()` must be greater than or equal to `old_layout.size()`
845+
/// If `current_memory` is `None`, this function is safe.
759846
// not marked inline(never) since we want optimizers to be able to observe the specifics of this
760847
// function, see tests/codegen-llvm/vec-reserve-extend.rs.
761848
#[cold]
762-
fnfinish_grow<A>(
849+
unsafefnfinish_grow<A>(
763850
new_layout:Layout,
764851
current_memory:Option<(NonNull<u8>,Layout)>,
765852
alloc:&mutA,

‎library/alloc/src/raw_vec/tests.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ struct ZST;
8585
fnzst_sanity<T>(v:&RawVec<T>){
8686
assert_eq!(v.capacity(),usize::MAX);
8787
assert_eq!(v.ptr(), core::ptr::Unique::<T>::dangling().as_ptr());
88-
assert_eq!(v.inner.current_memory(T::LAYOUT),None);
88+
assert_eq!(unsafe{v.inner.current_memory(T::LAYOUT)},None);
8989
}
9090

9191
#[test]
@@ -126,12 +126,12 @@ fn zst() {
126126
assert_eq!(v.try_reserve_exact(101,usize::MAX - 100), cap_err);
127127
zst_sanity(&v);
128128

129-
assert_eq!(v.inner.grow_amortized(100,usize::MAX - 100,ZST::LAYOUT), cap_err);
130-
assert_eq!(v.inner.grow_amortized(101,usize::MAX - 100,ZST::LAYOUT), cap_err);
129+
assert_eq!(unsafe{v.inner.grow_amortized(100,usize::MAX - 100,ZST::LAYOUT)}, cap_err);
130+
assert_eq!(unsafe{v.inner.grow_amortized(101,usize::MAX - 100,ZST::LAYOUT)}, cap_err);
131131
zst_sanity(&v);
132132

133-
assert_eq!(v.inner.grow_exact(100,usize::MAX - 100,ZST::LAYOUT), cap_err);
134-
assert_eq!(v.inner.grow_exact(101,usize::MAX - 100,ZST::LAYOUT), cap_err);
133+
assert_eq!(unsafe{v.inner.grow_exact(100,usize::MAX - 100,ZST::LAYOUT)}, cap_err);
134+
assert_eq!(unsafe{v.inner.grow_exact(101,usize::MAX - 100,ZST::LAYOUT)}, cap_err);
135135
zst_sanity(&v);
136136
}
137137

0 commit comments

Comments
 (0)