Skip to content

Add AliasingSafe framework #1224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ use core::{
},
};

use crate::pointer::invariant;
use crate::pointer::{invariant, BecauseExclusive, BecauseImmutable};

#[cfg(any(feature = "alloc", test))]
extern crate alloc;
Expand Down Expand Up @@ -1177,7 +1177,7 @@ pub unsafe trait TryFromBytes {
Self: KnownLayout + Immutable,
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_ref(candidate).try_cast_into_no_leftover::<Self>() {
match Ptr::from_ref(candidate).try_cast_into_no_leftover::<Self, BecauseImmutable>() {
Ok(candidate) => {
// This call may panic. If that happens, it doesn't cause any soundness
// issues, as we have not generated any invalid state which we need to
Expand All @@ -1189,7 +1189,9 @@ pub unsafe trait TryFromBytes {
// condition will not happen.
match candidate.try_into_valid() {
Ok(valid) => Ok(valid.as_ref()),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_ref()).into()),
Err(e) => {
Err(e.map_src(|src| src.as_bytes::<BecauseImmutable>().as_ref()).into())
}
}
}
Err(e) => Err(e.map_src(Ptr::as_ref).into()),
Expand Down Expand Up @@ -1428,7 +1430,7 @@ pub unsafe trait TryFromBytes {
Self: KnownLayout + Immutable, // TODO(#251): Remove the `Immutable` bound.
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<Self>() {
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<Self, BecauseExclusive>() {
Ok(candidate) => {
// This call may panic. If that happens, it doesn't cause any soundness
// issues, as we have not generated any invalid state which we need to
Expand All @@ -1440,7 +1442,9 @@ pub unsafe trait TryFromBytes {
// condition will not happen.
match candidate.try_into_valid() {
Ok(candidate) => Ok(candidate.as_mut()),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()),
Err(e) => {
Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into())
}
}
}
Err(e) => Err(e.map_src(Ptr::as_mut).into()),
Expand Down Expand Up @@ -1707,7 +1711,7 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
candidate: &[u8],
cast_type: CastType,
) -> Result<(&T, &[u8]), TryCastError<&[u8], T>> {
match Ptr::from_ref(candidate).try_cast_into::<T>(cast_type) {
match Ptr::from_ref(candidate).try_cast_into::<T, BecauseImmutable>(cast_type) {
Ok((candidate, prefix_suffix)) => {
// This call may panic. If that happens, it doesn't cause any soundness
// issues, as we have not generated any invalid state which we need to
Expand All @@ -1719,19 +1723,19 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
// condition will not happen.
match candidate.try_into_valid() {
Ok(valid) => Ok((valid.as_ref(), prefix_suffix.as_ref())),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_ref()).into()),
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseImmutable>().as_ref()).into()),
}
}
Err(e) => Err(e.map_src(Ptr::as_ref).into()),
}
}

#[inline(always)]
fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized>(
fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + ?Sized>(
candidate: &mut [u8],
cast_type: CastType,
) -> Result<(&mut T, &mut [u8]), TryCastError<&mut [u8], T>> {
match Ptr::from_mut(candidate).try_cast_into::<T>(cast_type) {
match Ptr::from_mut(candidate).try_cast_into::<T, BecauseExclusive>(cast_type) {
Ok((candidate, prefix_suffix)) => {
// This call may panic. If that happens, it doesn't cause any soundness
// issues, as we have not generated any invalid state which we need to
Expand All @@ -1743,7 +1747,7 @@ fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
// condition will not happen.
match candidate.try_into_valid() {
Ok(valid) => Ok((valid.as_mut(), prefix_suffix.as_mut())),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()),
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into()),
}
}
Err(e) => Err(e.map_src(Ptr::as_mut).into()),
Expand Down Expand Up @@ -2332,7 +2336,7 @@ pub unsafe trait FromBytes: FromZeros {
Self: KnownLayout + Immutable,
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_ref(bytes).try_cast_into_no_leftover() {
match Ptr::from_ref(bytes).try_cast_into_no_leftover::<_, BecauseImmutable>() {
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_ref()),
Err(err) => Err(err.map_src(|src| src.as_ref())),
}
Expand Down Expand Up @@ -2407,7 +2411,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, suffix) = Ptr::from_ref(bytes)
.try_cast_into(CastType::Prefix)
.try_cast_into::<_, BecauseImmutable>(CastType::Prefix)
.map_err(|err| err.map_src(|s| s.as_ref()))?;
Ok((slf.bikeshed_recall_valid().as_ref(), suffix.as_ref()))
}
Expand Down Expand Up @@ -2467,7 +2471,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, prefix) = Ptr::from_ref(bytes)
.try_cast_into(CastType::Suffix)
.try_cast_into::<_, BecauseImmutable>(CastType::Suffix)
.map_err(|err| err.map_src(|s| s.as_ref()))?;
Ok((prefix.as_ref(), slf.bikeshed_recall_valid().as_ref()))
}
Expand Down Expand Up @@ -2534,7 +2538,7 @@ pub unsafe trait FromBytes: FromZeros {
Self: IntoBytes + KnownLayout + Immutable,
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_mut(bytes).try_cast_into_no_leftover() {
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<_, BecauseExclusive>() {
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_mut()),
Err(err) => Err(err.map_src(|src| src.as_mut())),
}
Expand Down Expand Up @@ -2610,7 +2614,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, suffix) = Ptr::from_mut(bytes)
.try_cast_into(CastType::Prefix)
.try_cast_into::<_, BecauseExclusive>(CastType::Prefix)
.map_err(|err| err.map_src(|s| s.as_mut()))?;
Ok((slf.bikeshed_recall_valid().as_mut(), suffix.as_mut()))
}
Expand Down Expand Up @@ -2679,7 +2683,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, prefix) = Ptr::from_mut(bytes)
.try_cast_into(CastType::Suffix)
.try_cast_into::<_, BecauseExclusive>(CastType::Suffix)
.map_err(|err| err.map_src(|s| s.as_mut()))?;
Ok((prefix.as_mut(), slf.bikeshed_recall_valid().as_mut()))
}
Expand Down
89 changes: 89 additions & 0 deletions src/pointer/aliasing_safety.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.

//! Machinery for statically proving the "aliasing-safety" of a `Ptr`.

use crate::{invariant, Immutable};

/// Pointer conversions which do not violate aliasing.
///
/// `U: AliasingSafe<T, A, R>` implies that a pointer conversion from `T` to `U`
/// does not violate the aliasing invariant, `A`. This can be because `A` is
/// [`Exclusive`] or because neither `T` nor `U` permit interior mutability.
///
/// # Safety
///
/// `U: AliasingSafe<T, A, R>` if either of the following conditions holds:
/// - `A` is [`Exclusive`]
/// - `T` and `U` both implement [`Immutable`]
///
/// [`Exclusive`]: crate::pointer::invariant::Exclusive
#[doc(hidden)]
pub unsafe trait AliasingSafe<T: ?Sized, A: invariant::Aliasing, R: AliasingSafeReason> {}

/// Used to prevent user implementations of `AliasingSafeReason`.
mod sealed {
pub trait Sealed {}

impl Sealed for super::BecauseExclusive {}
impl Sealed for super::BecauseImmutable {}
impl<S: Sealed> Sealed for (S,) {}
}

#[doc(hidden)]
pub trait AliasingSafeReason: sealed::Sealed {}
impl<R: AliasingSafeReason> AliasingSafeReason for (R,) {}

/// The conversion is safe because only one live `Ptr` or reference may exist to
/// the referent bytes at a time.
#[derive(Copy, Clone, Debug)]
#[doc(hidden)]
pub enum BecauseExclusive {}
impl AliasingSafeReason for BecauseExclusive {}

/// The conversion is safe because no live `Ptr`s or references permit mutation.
#[derive(Copy, Clone, Debug)]
#[doc(hidden)]
pub enum BecauseImmutable {}
impl AliasingSafeReason for BecauseImmutable {}

/// SAFETY: `T: AliasingSafe<Exclusive, BecauseExclusive>` because for all
/// `Ptr<'a, T, I>` such that `I::Aliasing = Exclusive`, there cannot exist
/// other live references to the memory referenced by `Ptr`.
unsafe impl<T: ?Sized, U: ?Sized> AliasingSafe<T, invariant::Exclusive, BecauseExclusive> for U {}

/// SAFETY: `U: AliasingSafe<T, A, BecauseNoCell>` because for all `Ptr<'a, T,
/// I>` and `Ptr<'a, U, I>` such that `I::Aliasing = A`, all live references and
/// live `Ptr`s agree, by invariant on `Immutable`, that the referenced bytes
/// contain no `UnsafeCell`s, and thus do not permit mutation except via
/// exclusive aliasing.
unsafe impl<A, T: ?Sized, U: ?Sized> AliasingSafe<T, A, BecauseImmutable> for U
where
A: invariant::Aliasing,
T: Immutable,
U: Immutable,
{
}

/// This ensures that `U: AliasingSafe<T, A>` implies `T: AliasingSafe<U, A>` in
/// a manner legible to rustc, which in turn means we can write simpler bounds in
/// some places.
///
/// SAFETY: Per `U: AliasingSafe<T, A, R>`, either:
/// - `A` is `Exclusive`
/// - `T` and `U` both implement `Immutable`
///
/// Neither property depends on which of `T` and `U` are in the `Self` position
/// vs the first type parameter position.
unsafe impl<A, T: ?Sized, U: ?Sized, R> AliasingSafe<U, A, (R,)> for T
where
A: invariant::Aliasing,
R: AliasingSafeReason,
U: AliasingSafe<T, A, R>,
{
}
4 changes: 3 additions & 1 deletion src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

//! Abstractions over raw pointers.

mod aliasing_safety;
mod ptr;

pub use aliasing_safety::{AliasingSafe, BecauseExclusive, BecauseImmutable};
pub use ptr::{invariant, Ptr};

use crate::Unaligned;
Expand Down Expand Up @@ -70,5 +72,5 @@ where
I: invariant::Invariants<Validity = invariant::Initialized>,
I::Aliasing: invariant::AtLeast<invariant::Shared>,
{
ptr.as_bytes().as_ref().iter().all(|&byte| byte == 0)
ptr.as_bytes::<BecauseImmutable>().as_ref().iter().all(|&byte| byte == 0)
}
53 changes: 37 additions & 16 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use core::ptr::NonNull;

use crate::{util::AsAddress, CastType, Immutable, KnownLayout};
use crate::{util::AsAddress, CastType, KnownLayout};

/// Module used to gate access to [`Ptr`]'s fields.
mod def {
Expand Down Expand Up @@ -910,7 +910,10 @@ mod _transitions {
/// Casts of the referent type.
mod _casts {
use super::*;
use crate::{layout::MetadataCastError, AlignmentError, CastError, PointerMetadata, SizeError};
use crate::{
layout::MetadataCastError, pointer::aliasing_safety::*, AlignmentError, CastError,
PointerMetadata, SizeError,
};

impl<'a, T, I> Ptr<'a, T, I>
where
Expand Down Expand Up @@ -996,11 +999,14 @@ mod _casts {
where
T: 'a + KnownLayout + ?Sized,
I: Invariants<Validity = Initialized>,
T: Immutable,
{
/// Casts this pointer-to-initialized into a pointer-to-bytes.
#[allow(clippy::wrong_self_convention)]
pub(crate) fn as_bytes(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)> {
pub(crate) fn as_bytes<R>(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)>
where
[u8]: AliasingSafe<T, I::Aliasing, R>,
R: AliasingSafeReason,
{
let bytes = match T::size_of_val_raw(self.as_non_null()) {
Some(bytes) => bytes,
// SAFETY: `KnownLayout::size_of_val_raw` promises to always
Expand All @@ -1016,8 +1022,10 @@ mod _casts {
// pointer's address, and `bytes` is the length of `p`, so the
// returned pointer addresses the same bytes as `p`
// - `slice_from_raw_parts_mut` and `.cast` both preserve provenance
// - `T` and `[u8]` trivially contain `UnsafeCell`s at identical
// ranges [u8]`, because both are `Immutable`.
// - Because `[u8]: AliasingSafe<T, I::Aliasing, _>`, either:
// - `I::Aliasing` is `Exclusive`
// - `T` and `[u8]` are both `Immutable`, in which case they
// trivially contain `UnsafeCell`s at identical locations
let ptr: Ptr<'a, [u8], _> = unsafe {
self.cast_unsized(|p: *mut T| {
#[allow(clippy::as_conversions)]
Expand Down Expand Up @@ -1112,13 +1120,17 @@ mod _casts {
/// - If this is a prefix cast, `ptr` has the same address as `self`.
/// - If this is a suffix cast, `remainder` has the same address as
/// `self`.
pub(crate) fn try_cast_into<U: 'a + ?Sized + KnownLayout + Immutable>(
pub(crate) fn try_cast_into<U, R>(
self,
cast_type: CastType,
) -> Result<
(Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, Ptr<'a, [u8], I>),
CastError<Self, U>,
> {
>
where
R: AliasingSafeReason,
U: 'a + ?Sized + KnownLayout + AliasingSafe<[u8], I::Aliasing, R>,
{
crate::util::assert_dst_is_not_zst::<U>();
// PANICS: By invariant, the byte range addressed by `self.ptr` does
// not wrap around the address space. This implies that the sum of
Expand Down Expand Up @@ -1172,9 +1184,12 @@ mod _casts {
// does not wrap around the address space, so does `ptr`.
// 5. Since, by invariant, `target` refers to an allocation which
// is guaranteed to live for at least `'a`, so does `ptr`.
// 6. Since, by invariant, `target` conforms to the aliasing
// invariant of `I::Aliasing` with regards to its referent
// bytes, so does `ptr`.
// 6. Since `U: AliasingSafe<[u8], I::Aliasing, _>`, either:
// - `I::Aliasing` is `Exclusive`, in which case both `src`
// and `ptr` conform to `Exclusive`
// - `I::Aliasing` is `Shared` or `Any` and both `U` and
// `[u8]` are `Immutable`. In this case, neither pointer
// permits mutation, and so `Shared` aliasing is satisfied.
// 7. `ptr` conforms to the alignment invariant of `Aligned` because
// it is derived from `validate_cast_and_convert_metadata`, which
// promises that the object described by `target` is validly
Expand All @@ -1198,9 +1213,13 @@ mod _casts {
/// references the same byte range as `self`.
#[allow(unused)]
#[inline(always)]
pub(crate) fn try_cast_into_no_leftover<U: 'a + ?Sized + KnownLayout + Immutable>(
pub(crate) fn try_cast_into_no_leftover<U, R>(
self,
) -> Result<Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, CastError<Self, U>> {
) -> Result<Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, CastError<Self, U>>
where
U: 'a + ?Sized + KnownLayout + AliasingSafe<[u8], I::Aliasing, R>,
R: AliasingSafeReason,
{
// TODO(#67): Remove this allow. See NonNulSlicelExt for more
// details.
#[allow(unstable_name_collisions)]
Expand Down Expand Up @@ -1459,7 +1478,7 @@ mod tests {
use static_assertions::{assert_impl_all, assert_not_impl_any};

use super::*;
use crate::{util::testutil::AU64, FromBytes};
use crate::{pointer::BecauseImmutable, util::testutil::AU64, FromBytes, Immutable};

#[test]
fn test_split_at() {
Expand Down Expand Up @@ -1546,7 +1565,7 @@ mod tests {

for cast_type in [CastType::Prefix, CastType::Suffix] {
if let Ok((slf, remaining)) =
Ptr::from_ref(bytes).try_cast_into::<T>(cast_type)
Ptr::from_ref(bytes).try_cast_into::<T, BecauseImmutable>(cast_type)
{
// SAFETY: All bytes in `bytes` have been
// initialized.
Expand All @@ -1563,7 +1582,9 @@ mod tests {
}
}

if let Ok(slf) = Ptr::from_ref(bytes).try_cast_into_no_leftover::<T>() {
if let Ok(slf) =
Ptr::from_ref(bytes).try_cast_into_no_leftover::<T, BecauseImmutable>()
{
// SAFETY: All bytes in `bytes` have been
// initialized.
let len = unsafe { validate_and_get_len(slf) };
Expand Down
Loading
Loading