From 005ee3254fdfa63d67d024cfc598319331dab0cb Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:24:43 +0000 Subject: [PATCH] Fix panics in `try_cast_slice_box` etc (#254) * Fix panics in try_cast_slice_{box,rc,arc},cast_vec. Casting from non-empty non-ZST slices to ZST slices now returns Err(SizeMismatch) (instead of panicking). Casting from empty non-ZST slices to ZST slices is allowed and returns an empty slice (instead of panicking). Casting from ZST slices to non-ZST slices is allowed and returns an empty slice (status quo). * Add tests for cast_slice_box,rc,arc. --- src/allocation.rs | 40 ++++++++---- tests/cast_slice_tests.rs | 133 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 11 deletions(-) diff --git a/src/allocation.rs b/src/allocation.rs index 1763ffd..ae0dca1 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -21,7 +21,7 @@ use alloc::{ vec::Vec, }; use core::{ - mem::ManuallyDrop, + mem::{size_of_val, ManuallyDrop}, ops::{Deref, DerefMut}, }; @@ -172,7 +172,10 @@ pub fn try_cast_slice_box( if align_of::() != align_of::() { Err((PodCastError::AlignmentMismatch, input)) } else if size_of::() != size_of::() { - if size_of::() * input.len() % size_of::() != 0 { + let input_bytes = size_of_val::<[A]>(&*input); + if (size_of::() == 0 && input_bytes != 0) + || (size_of::() != 0 && input_bytes % size_of::() != 0) + { // If the size in bytes of the underlying buffer does not match an exact // multiple of the size of B, we cannot cast between them. Err((PodCastError::SizeMismatch, input)) @@ -185,7 +188,8 @@ pub fn try_cast_slice_box( // Luckily, Layout only stores two things, the alignment, and the size in // bytes. So as long as both of those stay the same, the Layout will // remain a valid input to dealloc. - let length = size_of::() * input.len() / size_of::(); + let length = + if size_of::() != 0 { input_bytes / size_of::() } else { 0 }; let box_ptr: *mut A = Box::into_raw(input) as *mut A; let ptr: *mut [B] = unsafe { core::slice::from_raw_parts_mut(box_ptr as *mut B, length) }; @@ -222,8 +226,12 @@ pub fn try_cast_vec( if align_of::() != align_of::() { Err((PodCastError::AlignmentMismatch, input)) } else if size_of::() != size_of::() { - if size_of::() * input.len() % size_of::() != 0 - || size_of::() * input.capacity() % size_of::() != 0 + let input_size = size_of_val::<[A]>(&*input); + let input_capacity = input.capacity() * size_of::(); + if (size_of::() == 0 && input_capacity != 0) + || (size_of::() != 0 + && (input_size % size_of::() != 0 + || input_capacity % size_of::() != 0)) { // If the size in bytes of the underlying buffer does not match an exact // multiple of the size of B, we cannot cast between them. @@ -244,8 +252,10 @@ pub fn try_cast_vec( // Note(Lokathor): First we record the length and capacity, which don't // have any secret provenance metadata. - let length: usize = size_of::() * input.len() / size_of::(); - let capacity: usize = size_of::() * input.capacity() / size_of::(); + let length: usize = + if size_of::() != 0 { input_size / size_of::() } else { 0 }; + let capacity: usize = + if size_of::() != 0 { input_capacity / size_of::() } else { 0 }; // Note(Lokathor): Next we "pre-forget" the old Vec by wrapping with // ManuallyDrop, because if we used `core::mem::forget` after taking the // pointer then that would invalidate our pointer. In nightly there's a @@ -415,7 +425,10 @@ pub fn try_cast_slice_rc< if align_of::() != align_of::() { Err((PodCastError::AlignmentMismatch, input)) } else if size_of::() != size_of::() { - if size_of::() * input.len() % size_of::() != 0 { + let input_bytes = size_of_val::<[A]>(&*input); + if (size_of::() == 0 && input_bytes != 0) + || (size_of::() != 0 && input_bytes % size_of::() != 0) + { // If the size in bytes of the underlying buffer does not match an exact // multiple of the size of B, we cannot cast between them. Err((PodCastError::SizeMismatch, input)) @@ -427,7 +440,8 @@ pub fn try_cast_slice_rc< // acquired from Rc::into_raw() must have the same size alignment and // size of the type T in the new Rc. So as long as both the size // and alignment stay the same, the Rc will remain a valid Rc. - let length = size_of::() * input.len() / size_of::(); + let length = + if size_of::() != 0 { input_bytes / size_of::() } else { 0 }; let rc_ptr: *const A = Rc::into_raw(input) as *const A; // Must use ptr::slice_from_raw_parts, because we cannot make an // intermediate const reference, because it has mutable provenance, @@ -479,7 +493,10 @@ pub fn try_cast_slice_arc< if align_of::() != align_of::() { Err((PodCastError::AlignmentMismatch, input)) } else if size_of::() != size_of::() { - if size_of::() * input.len() % size_of::() != 0 { + let input_bytes = size_of_val::<[A]>(&*input); + if (size_of::() == 0 && input_bytes != 0) + || (size_of::() != 0 && input_bytes % size_of::() != 0) + { // If the size in bytes of the underlying buffer does not match an exact // multiple of the size of B, we cannot cast between them. Err((PodCastError::SizeMismatch, input)) @@ -491,7 +508,8 @@ pub fn try_cast_slice_arc< // acquired from Arc::into_raw() must have the same size alignment and // size of the type T in the new Arc. So as long as both the size // and alignment stay the same, the Arc will remain a valid Arc. - let length = size_of::() * input.len() / size_of::(); + let length = + if size_of::() != 0 { input_bytes / size_of::() } else { 0 }; let arc_ptr: *const A = Arc::into_raw(input) as *const A; // Must use ptr::slice_from_raw_parts, because we cannot make an // intermediate const reference, because it has mutable provenance, diff --git a/tests/cast_slice_tests.rs b/tests/cast_slice_tests.rs index ce6dedc..71bc4df 100644 --- a/tests/cast_slice_tests.rs +++ b/tests/cast_slice_tests.rs @@ -195,3 +195,136 @@ fn test_panics() { let aligned_bytes = bytemuck::cast_slice::(&[0, 0]); should_panic!(from_bytes::(&aligned_bytes[1..5])); } + +#[cfg(feature = "extern_crate_alloc")] +#[test] +fn test_boxed_slices() { + let boxed_u8_slice: Box<[u8]> = Box::new([0, 1, u8::MAX, i8::MAX as u8]); + let boxed_i8_slice: Box<[i8]> = cast_slice_box::(boxed_u8_slice); + assert_eq!(&*boxed_i8_slice, [0, 1, -1, i8::MAX]); + + let result: Result, (PodCastError, Box<[i8]>)> = + try_cast_slice_box(boxed_i8_slice); + let (error, boxed_i8_slice) = + result.expect_err("u16 and i8 have different alignment"); + assert_eq!(error, PodCastError::AlignmentMismatch); + + // FIXME(#253): Should these next two casts' errors be consistent? + let result: Result<&[[i8; 3]], PodCastError> = + try_cast_slice(&*boxed_i8_slice); + let error = + result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s"); + assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop); + + let result: Result, (PodCastError, Box<[i8]>)> = + try_cast_slice_box(boxed_i8_slice); + let (error, boxed_i8_slice) = + result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s"); + assert_eq!(error, PodCastError::SizeMismatch); + + let empty: Box<[()]> = cast_slice_box::(Box::new([])); + assert!(empty.is_empty()); + + let result: Result, (PodCastError, Box<[i8]>)> = + try_cast_slice_box(boxed_i8_slice); + let (error, boxed_i8_slice) = + result.expect_err("slice of ZST cannot be made from slice of 4 u8s"); + assert_eq!(error, PodCastError::SizeMismatch); + + drop(boxed_i8_slice); + + let empty: Box<[i8]> = cast_slice_box::<(), i8>(Box::new([])); + assert!(empty.is_empty()); + + let empty: Box<[i8]> = cast_slice_box::<(), i8>(Box::new([(); 42])); + assert!(empty.is_empty()); +} + +#[cfg(feature = "extern_crate_alloc")] +#[test] +fn test_rc_slices() { + use std::rc::Rc; + let rc_u8_slice: Rc<[u8]> = Rc::new([0, 1, u8::MAX, i8::MAX as u8]); + let rc_i8_slice: Rc<[i8]> = cast_slice_rc::(rc_u8_slice); + assert_eq!(&*rc_i8_slice, [0, 1, -1, i8::MAX]); + + let result: Result, (PodCastError, Rc<[i8]>)> = + try_cast_slice_rc(rc_i8_slice); + let (error, rc_i8_slice) = + result.expect_err("u16 and i8 have different alignment"); + assert_eq!(error, PodCastError::AlignmentMismatch); + + // FIXME(#253): Should these next two casts' errors be consistent? + let result: Result<&[[i8; 3]], PodCastError> = try_cast_slice(&*rc_i8_slice); + let error = + result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s"); + assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop); + + let result: Result, (PodCastError, Rc<[i8]>)> = + try_cast_slice_rc(rc_i8_slice); + let (error, rc_i8_slice) = + result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s"); + assert_eq!(error, PodCastError::SizeMismatch); + + let empty: Rc<[()]> = cast_slice_rc::(Rc::new([])); + assert!(empty.is_empty()); + + let result: Result, (PodCastError, Rc<[i8]>)> = + try_cast_slice_rc(rc_i8_slice); + let (error, rc_i8_slice) = + result.expect_err("slice of ZST cannot be made from slice of 4 u8s"); + assert_eq!(error, PodCastError::SizeMismatch); + + drop(rc_i8_slice); + + let empty: Rc<[i8]> = cast_slice_rc::<(), i8>(Rc::new([])); + assert!(empty.is_empty()); + + let empty: Rc<[i8]> = cast_slice_rc::<(), i8>(Rc::new([(); 42])); + assert!(empty.is_empty()); +} + +#[cfg(feature = "extern_crate_alloc")] +#[cfg(target_has_atomic = "ptr")] +#[test] +fn test_arc_slices() { + use std::sync::Arc; + let arc_u8_slice: Arc<[u8]> = Arc::new([0, 1, u8::MAX, i8::MAX as u8]); + let arc_i8_slice: Arc<[i8]> = cast_slice_arc::(arc_u8_slice); + assert_eq!(&*arc_i8_slice, [0, 1, -1, i8::MAX]); + + let result: Result, (PodCastError, Arc<[i8]>)> = + try_cast_slice_arc(arc_i8_slice); + let (error, arc_i8_slice) = + result.expect_err("u16 and i8 have different alignment"); + assert_eq!(error, PodCastError::AlignmentMismatch); + + // FIXME(#253): Should these next two casts' errors be consistent? + let result: Result<&[[i8; 3]], PodCastError> = try_cast_slice(&*arc_i8_slice); + let error = + result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s"); + assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop); + + let result: Result, (PodCastError, Arc<[i8]>)> = + try_cast_slice_arc(arc_i8_slice); + let (error, arc_i8_slice) = + result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s"); + assert_eq!(error, PodCastError::SizeMismatch); + + let empty: Arc<[()]> = cast_slice_arc::(Arc::new([])); + assert!(empty.is_empty()); + + let result: Result, (PodCastError, Arc<[i8]>)> = + try_cast_slice_arc(arc_i8_slice); + let (error, arc_i8_slice) = + result.expect_err("slice of ZST cannot be made from slice of 4 u8s"); + assert_eq!(error, PodCastError::SizeMismatch); + + drop(arc_i8_slice); + + let empty: Arc<[i8]> = cast_slice_arc::<(), i8>(Arc::new([])); + assert!(empty.is_empty()); + + let empty: Arc<[i8]> = cast_slice_arc::<(), i8>(Arc::new([(); 42])); + assert!(empty.is_empty()); +}