Skip to content

Commit db19ca3

Browse files
Strengthen Box from_raw layout contracts and slice harnesses
Documented Box reconstruction requires more than non-null dereference: size must fit isize::MAX. Encode that with a kani-only layout check on from_raw / from_non_null / from_raw_in / from_non_null_in. Use symbolic slice lengths (capped at 2) on reconstructors and slice constructors, and add from_raw_in / from_non_null_in slice harnesses.
1 parent 5d8f0ee commit db19ca3

1 file changed

Lines changed: 79 additions & 23 deletions

File tree

library/alloc/src/boxed.rs

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ use core::ub_checks;
211211

212212
use safety::{ensures, requires};
213213

214+
/// Kani-only: documented Box raw-pointer layout besides “valid `T`”.
215+
/// `can_dereference` covers aligned/initialized; Box also forbids size > `isize::MAX`.
216+
/// Allocator provenance is a caller obligation we cannot express.
217+
#[cfg(kani)]
218+
fn box_ptr_fits_box_layout<T: ?Sized>(ptr: *const T) -> bool {
219+
ub_checks::can_dereference(ptr) && {
220+
// SAFETY: `can_dereference` means `ptr` is a valid `T`, so size/align exist.
221+
let layout = unsafe { Layout::for_value_raw(ptr) };
222+
layout.size() <= isize::MAX as usize
223+
}
224+
}
225+
214226
#[cfg(not(no_global_oom_handling))]
215227
use crate::alloc::handle_alloc_error;
216228
use crate::alloc::{AllocError, Allocator, Global, Layout};
@@ -1157,10 +1169,10 @@ impl<T: ?Sized> Box<T> {
11571169
#[stable(feature = "box_raw", since = "1.4.0")]
11581170
#[inline]
11591171
#[must_use = "call `drop(Box::from_raw(ptr))` if you intend to drop the `Box`"]
1160-
// Memory-layout contract: non-null, aligned, in-bounds, and a valid `T`
1161-
// (including ZST dangling pointers). Provenance/allocator identity is a
1162-
// caller obligation Kani cannot fully express.
1163-
#[requires(!raw.is_null() && ub_checks::can_dereference(raw))]
1172+
// Memory-layout contract: non-null, aligned, in-bounds, valid `T`,
1173+
// size ≤ isize::MAX (including ZST dangling pointers).
1174+
// Provenance/allocator identity is a caller obligation Kani cannot fully express.
1175+
#[requires(!raw.is_null() && box_ptr_fits_box_layout(raw))]
11641176
#[ensures(|result| (&**result) as *const T == raw as *const T)]
11651177
pub unsafe fn from_raw(raw: *mut T) -> Self {
11661178
unsafe { Self::from_raw_in(raw, Global) }
@@ -1216,7 +1228,7 @@ impl<T: ?Sized> Box<T> {
12161228
#[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")]
12171229
#[inline]
12181230
#[must_use = "call `drop(Box::from_non_null(ptr))` if you intend to drop the `Box`"]
1219-
#[requires(ub_checks::can_dereference(ptr.as_ptr()))]
1231+
#[requires(box_ptr_fits_box_layout(ptr.as_ptr()))]
12201232
#[ensures(|result| (&**result) as *const T == ptr.as_ptr() as *const T)]
12211233
pub unsafe fn from_non_null(ptr: NonNull<T>) -> Self {
12221234
unsafe { Self::from_raw(ptr.as_ptr()) }
@@ -1391,7 +1403,7 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
13911403
/// [memory layout]: self#memory-layout
13921404
#[unstable(feature = "allocator_api", issue = "32838")]
13931405
#[inline]
1394-
#[requires(!raw.is_null() && ub_checks::can_dereference(raw))]
1406+
#[requires(!raw.is_null() && box_ptr_fits_box_layout(raw))]
13951407
#[ensures(|result| (&**result) as *const T == raw as *const T)]
13961408
pub unsafe fn from_raw_in(raw: *mut T, alloc: A) -> Self {
13971409
Box(unsafe { Unique::new_unchecked(raw) }, alloc)
@@ -1446,7 +1458,7 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
14461458
#[unstable(feature = "allocator_api", issue = "32838")]
14471459
// #[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")]
14481460
#[inline]
1449-
#[requires(ub_checks::can_dereference(raw.as_ptr()))]
1461+
#[requires(box_ptr_fits_box_layout(raw.as_ptr()))]
14501462
#[ensures(|result| (&**result) as *const T == raw.as_ptr() as *const T)]
14511463
pub unsafe fn from_non_null_in(raw: NonNull<T>, alloc: A) -> Self {
14521464
// SAFETY: guaranteed by the caller.
@@ -2422,10 +2434,12 @@ mod verify {
24222434
#[kani::proof_for_contract(Box::<[u8]>::from_raw)]
24232435
pub fn check_from_raw_slice() {
24242436
let data: [u8; SLICE_CAP] = kani::any();
2425-
let boxed: Box<[u8]> = Box::from(data);
2437+
let slice = kani::slice::any_slice_of_array(&data);
2438+
let boxed: Box<[u8]> = Box::from(slice);
2439+
let len = boxed.len();
24262440
let ptr = Box::into_raw(boxed);
24272441
let boxed = unsafe { Box::<[u8]>::from_raw(ptr) };
2428-
assert!(boxed.len() == SLICE_CAP);
2442+
assert!(boxed.len() == len);
24292443
}
24302444

24312445
// ---- required unsafe: from_non_null ----
@@ -2447,10 +2461,12 @@ mod verify {
24472461
#[kani::proof_for_contract(Box::<[u8]>::from_non_null)]
24482462
pub fn check_from_non_null_slice() {
24492463
let data: [u8; SLICE_CAP] = kani::any();
2450-
let boxed: Box<[u8]> = Box::from(data);
2464+
let slice = kani::slice::any_slice_of_array(&data);
2465+
let boxed: Box<[u8]> = Box::from(slice);
2466+
let len = boxed.len();
24512467
let ptr = Box::into_non_null(boxed);
24522468
let boxed = unsafe { Box::<[u8]>::from_non_null(ptr) };
2453-
assert!(boxed.len() == SLICE_CAP);
2469+
assert!(boxed.len() == len);
24542470
}
24552471

24562472
// ---- required unsafe: from_raw_in ----
@@ -2470,6 +2486,17 @@ mod verify {
24702486
let _boxed = unsafe { Box::from_raw_in(ptr, Global) };
24712487
}
24722488

2489+
#[kani::proof_for_contract(Box::<[u8], Global>::from_raw_in)]
2490+
pub fn check_from_raw_in_slice() {
2491+
let data: [u8; SLICE_CAP] = kani::any();
2492+
let slice = kani::slice::any_slice_of_array(&data);
2493+
let boxed: Box<[u8]> = Box::from(slice);
2494+
let len = boxed.len();
2495+
let (ptr, alloc) = Box::into_raw_with_allocator(boxed);
2496+
let boxed = unsafe { Box::from_raw_in(ptr, alloc) };
2497+
assert!(boxed.len() == len);
2498+
}
2499+
24732500
// ---- required unsafe: from_non_null_in ----
24742501

24752502
#[kani::proof_for_contract(Box::<i32, Global>::from_non_null_in)]
@@ -2486,6 +2513,17 @@ mod verify {
24862513
let _boxed = unsafe { Box::from_non_null_in(ptr, Global) };
24872514
}
24882515

2516+
#[kani::proof_for_contract(Box::<[u8], Global>::from_non_null_in)]
2517+
pub fn check_from_non_null_in_slice() {
2518+
let data: [u8; SLICE_CAP] = kani::any();
2519+
let slice = kani::slice::any_slice_of_array(&data);
2520+
let boxed: Box<[u8]> = Box::from(slice);
2521+
let len = boxed.len();
2522+
let (ptr, alloc) = Box::into_non_null_with_allocator(boxed);
2523+
let boxed = unsafe { Box::from_non_null_in(ptr, alloc) };
2524+
assert!(boxed.len() == len);
2525+
}
2526+
24892527
// ---- safe functions with unsafe bodies ----
24902528

24912529
#[kani::proof]
@@ -2546,17 +2584,24 @@ mod verify {
25462584
}
25472585

25482586
#[kani::proof]
2587+
#[kani::unwind(4)]
25492588
pub fn check_try_new_uninit_slice() {
2550-
let slot = Box::<[i32]>::try_new_uninit_slice(1).expect("alloc");
2551-
assert!(slot.len() == 1);
2589+
let len = kani::any_where(|n: &usize| *n <= SLICE_CAP);
2590+
let slot = Box::<[i32]>::try_new_uninit_slice(len).expect("alloc");
2591+
assert!(slot.len() == len);
25522592
assert!(Box::<[u64]>::try_new_uninit_slice(usize::MAX).is_err());
25532593
}
25542594

25552595
#[kani::proof]
2596+
#[kani::unwind(4)]
25562597
pub fn check_try_new_zeroed_slice() {
2557-
let slot = Box::<[u8]>::try_new_zeroed_slice(1).expect("alloc");
2598+
let len = kani::any_where(|n: &usize| *n <= SLICE_CAP);
2599+
let slot = Box::<[u8]>::try_new_zeroed_slice(len).expect("alloc");
25582600
let boxed = unsafe { slot.assume_init() };
2559-
assert!(boxed[0] == 0);
2601+
assert!(boxed.len() == len);
2602+
for i in 0..len {
2603+
assert!(boxed[i] == 0);
2604+
}
25602605
assert!(Box::<[u64]>::try_new_zeroed_slice(usize::MAX).is_err());
25612606
}
25622607

@@ -2573,28 +2618,39 @@ mod verify {
25732618
}
25742619

25752620
#[kani::proof]
2621+
#[kani::unwind(4)]
25762622
pub fn check_new_uninit_slice_in() {
2577-
let slot: Box<[MaybeUninit<i32>], _> = Box::new_uninit_slice_in(1, Global);
2578-
assert!(slot.len() == 1);
2623+
let len = kani::any_where(|n: &usize| *n <= SLICE_CAP);
2624+
let slot: Box<[MaybeUninit<i32>], _> = Box::new_uninit_slice_in(len, Global);
2625+
assert!(slot.len() == len);
25792626
}
25802627

25812628
#[kani::proof]
2629+
#[kani::unwind(4)]
25822630
pub fn check_new_zeroed_slice_in() {
2583-
let slot: Box<[MaybeUninit<u8>], _> = Box::new_zeroed_slice_in(1, Global);
2631+
let len = kani::any_where(|n: &usize| *n <= SLICE_CAP);
2632+
let slot: Box<[MaybeUninit<u8>], _> = Box::new_zeroed_slice_in(len, Global);
25842633
let boxed = unsafe { slot.assume_init() };
2585-
assert!(boxed[0] == 0);
2634+
assert!(boxed.len() == len);
2635+
for i in 0..len {
2636+
assert!(boxed[i] == 0);
2637+
}
25862638
}
25872639

25882640
#[kani::proof]
2641+
#[kani::unwind(4)]
25892642
pub fn check_try_new_uninit_slice_in() {
2590-
let slot = Box::<[i32]>::try_new_uninit_slice_in(0, Global).expect("zst/empty");
2591-
assert!(slot.is_empty());
2643+
let len = kani::any_where(|n: &usize| *n <= SLICE_CAP);
2644+
let slot = Box::<[i32]>::try_new_uninit_slice_in(len, Global).expect("alloc");
2645+
assert!(slot.len() == len);
25922646
}
25932647

25942648
#[kani::proof]
2649+
#[kani::unwind(4)]
25952650
pub fn check_try_new_zeroed_slice_in() {
2596-
let slot = Box::<[u8]>::try_new_zeroed_slice_in(1, Global).expect("alloc");
2597-
assert!(slot.len() == 1);
2651+
let len = kani::any_where(|n: &usize| *n <= SLICE_CAP);
2652+
let slot = Box::<[u8]>::try_new_zeroed_slice_in(len, Global).expect("alloc");
2653+
assert!(slot.len() == len);
25982654
}
25992655

26002656
#[kani::proof]

0 commit comments

Comments
 (0)