rusticl/api: Add checking wrappers around slice::from_raw_parts{_mut}

They check for null, alignment, excessive size, and address space wrapping. If any of the checks
fails, `Err(CL_INVALID_VALUE)` is returned.

The caller still has to uphold the other requirements of the `from_raw_parts` fns.

Reviewed-by: Karol Herbst <kherbst@redhat.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/26157>
This commit is contained in:
LingMan 2023-11-13 04:35:19 +01:00 committed by Marge Bot
parent 572a96aa59
commit 471d89c4fd
2 changed files with 98 additions and 0 deletions

View file

@ -365,3 +365,69 @@ pub fn check_copy_overlap(
/* Otherwise src and dst overlap. */
true
}
#[allow(dead_code)]
pub mod cl_slice {
use crate::api::util::CLResult;
use mesa_rust_util::ptr::addr;
use rusticl_opencl_gen::CL_INVALID_VALUE;
use std::mem;
use std::slice;
/// Wrapper around [`std::slice::from_raw_parts`] that returns `Err(CL_INVALID_VALUE)` if any of these conditions is met:
/// - `data` is null
/// - `data` is not correctly aligned for `T`
/// - `len * std::mem::size_of::<T>()` is larger than `isize::MAX`
/// - `data` + `len * std::mem::size_of::<T>()` wraps around the address space
///
/// # Safety
/// The behavior is undefined if any of the other requirements imposed by
/// [`std::slice::from_raw_parts`] is violated.
#[inline]
pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> CLResult<&'a [T]> {
if allocation_obviously_invalid(data, len) {
return Err(CL_INVALID_VALUE);
}
// SAFETY: We've checked that `data` is not null and properly aligned. We've also checked
// that the total size in bytes does not exceed `isize::MAX` and that adding that size to
// `data` does not wrap around the address space.
//
// The caller has to uphold the other safety requirements imposed by [`std::slice::from_raw_parts`].
unsafe { Ok(slice::from_raw_parts(data, len)) }
}
/// Wrapper around [`std::slice::from_raw_parts_mut`] that returns `Err(CL_INVALID_VALUE)` if any of these conditions is met:
/// - `data` is null
/// - `data` is not correctly aligned for `T`
/// - `len * std::mem::size_of::<T>()` is larger than `isize::MAX`
/// - `data` + `len * std::mem::size_of::<T>()` wraps around the address space
///
/// # Safety
/// The behavior is undefined if any of the other requirements imposed by
/// [`std::slice::from_raw_parts_mut`] is violated.
#[inline]
pub unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> CLResult<&'a mut [T]> {
if allocation_obviously_invalid(data, len) {
return Err(CL_INVALID_VALUE);
}
// SAFETY: We've checked that `data` is not null and properly aligned. We've also checked
// that the total size in bytes does not exceed `isize::MAX` and that adding that size to
// `data` does not wrap around the address space.
//
// The caller has to uphold the other safety requirements imposed by [`std::slice::from_raw_parts_mut`].
unsafe { Ok(slice::from_raw_parts_mut(data, len)) }
}
#[must_use]
fn allocation_obviously_invalid<T>(data: *const T, len: usize) -> bool {
let Some(total_size) = mem::size_of::<T>().checked_mul(len) else {
return true;
};
data.is_null()
|| !mesa_rust_util::ptr::is_aligned(data)
|| total_size > isize::MAX as usize
|| addr(data).checked_add(total_size).is_none()
}
}

View file

@ -1,3 +1,4 @@
use std::mem;
use std::ptr;
pub trait CheckedPtr<T> {
@ -52,3 +53,34 @@ macro_rules! offset_of {
offset()
}};
}
// Adapted from libstd since std::ptr::is_aligned is still unstable
// See https://github.com/rust-lang/rust/issues/96284
#[must_use]
#[inline]
pub const fn is_aligned<T>(ptr: *const T) -> bool
where
T: Sized,
{
let align = mem::align_of::<T>();
addr(ptr) & (align - 1) == 0
}
// Adapted from libstd since std::ptr::addr is still unstable
// See https://github.com/rust-lang/rust/issues/95228
#[must_use]
#[inline(always)]
pub const fn addr<T>(ptr: *const T) -> usize {
// The libcore implementations of `addr` and `expose_addr` suggest that, while both transmuting
// and casting to usize will give you the address of a ptr in the end, they are not identical
// in their side-effects.
// A cast "exposes" a ptr, which can potentially cause the compiler to optimize less
// aggressively around it.
// Let's trust the libcore devs over clippy on whether a transmute also exposes a ptr.
#[allow(clippy::transmutes_expressible_as_ptr_casts)]
// SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the
// provenance).
unsafe {
mem::transmute(ptr.cast::<()>())
}
}