Skip to content

Commit

Permalink
Merge #398
Browse files Browse the repository at this point in the history
398: [PLAT-838] Refactor `ipc_queue` backport r=jethrogb a=raoulstrackx

Backports #396

Co-authored-by: Raoul Strackx <raoul.strackx@fortanix.com>
  • Loading branch information
bors[bot] and raoulstrackx authored Jun 2, 2022
2 parents d7632ef + 8117cb3 commit 0837fce
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 44 deletions.
126 changes: 90 additions & 36 deletions ipc-queue/src/fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,48 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::mem;
use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
#[cfg(not(target_env = "sgx"))]
use {
std::sync::atomic::AtomicU64,
std::sync::Arc,
};
use std::sync::atomic::{AtomicUsize, Ordering, Ordering::SeqCst};

use fortanix_sgx_abi::{FifoDescriptor, WithId};

use super::*;

// `fortanix_sgx_abi::WithId` is not `Copy` because it contains an `AtomicU64`.
// This type has the same memory layout but is `Copy` and can be marked as
// `UserSafeSized` which is needed for the `User::from_raw_parts()` below.
#[cfg(target_env = "sgx")]
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct UserSafeWithId<T> {
pub id: u64,
pub data: T,
}

#[cfg(target_env = "sgx")]
unsafe impl<T: UserSafeSized> UserSafeSized for UserSafeWithId<T> {}

#[cfg(target_env = "sgx")]
unsafe fn _sanity_check_with_id() {
use std::mem::size_of;
let _: [u8; size_of::<fortanix_sgx_abi::WithId<()>>()] = [0u8; size_of::<UserSafeWithId<()>>()];
}

#[cfg(target_env = "sgx")]
#[repr(transparent)]
#[derive(Copy, Clone)]
struct WrapUsize(usize);

#[cfg(target_env = "sgx")]
unsafe impl UserSafeSized for WrapUsize{}

#[cfg(not(target_env = "sgx"))]
pub fn bounded<T, S>(len: usize, s: S) -> (Sender<T, S>, Receiver<T, S>)
where
T: Transmittable,
Expand All @@ -25,6 +59,7 @@ where
(tx, rx)
}

#[cfg(not(target_env = "sgx"))]
pub fn bounded_async<T, S>(len: usize, s: S) -> (AsyncSender<T, S>, AsyncReceiver<T, S>)
where
T: Transmittable,
Expand All @@ -33,15 +68,47 @@ where
let arc = Arc::new(FifoBuffer::new(len));
let inner = Fifo::from_arc(arc);
let tx = AsyncSender { inner: inner.clone(), synchronizer: s.clone() };
let rx = AsyncReceiver { inner, synchronizer: s, read_epoch: Arc::new(AtomicU32::new(0)) };
let rx = AsyncReceiver { inner, synchronizer: s, read_epoch: Arc::new(AtomicU64::new(0)) };
(tx, rx)
}

#[cfg(all(test, target_env = "sgx"))]
pub(crate) fn bounded<T, S>(len: usize, s: S) -> (Sender<T, S>, Receiver<T, S>)
where
T: Transmittable,
S: Synchronizer,
{
use std::ops::DerefMut;
use std::os::fortanix_sgx::usercalls::alloc::User;

// Allocate [WithId<T>; len] in userspace
// WARNING: This creates dangling memory in userspace, use in tests only!
let mut data = User::<[UserSafeWithId<T>]>::uninitialized(len);
data.deref_mut().iter_mut().for_each(|v| v.copy_from_enclave(&UserSafeWithId::default()));

// WARNING: This creates dangling memory in userspace, use in tests only!
let offsets = User::<WrapUsize>::new_from_enclave(&WrapUsize(0));
let offsets = offsets.into_raw() as *const AtomicUsize;

let descriptor = FifoDescriptor {
data: data.into_raw() as _,
len,
offsets,
};

let inner = unsafe { Fifo::from_descriptor(descriptor) };
let tx = Sender { inner: inner.clone(), synchronizer: s.clone() };
let rx = Receiver { inner, synchronizer: s };
(tx, rx)
}

#[cfg(not(target_env = "sgx"))]
pub(crate) struct FifoBuffer<T> {
data: Box<[WithId<T>]>,
offsets: Box<AtomicUsize>,
}

#[cfg(not(target_env = "sgx"))]
impl<T: Transmittable> FifoBuffer<T> {
fn new(len: usize) -> Self {
assert!(
Expand All @@ -57,16 +124,18 @@ impl<T: Transmittable> FifoBuffer<T> {
}
}

enum Storage<T> {
enum Storage<T: 'static> {
#[cfg(not(target_env = "sgx"))]
Shared(Arc<FifoBuffer<T>>),
Static,
Static(PhantomData<&'static T>),
}

impl<T> Clone for Storage<T> {
fn clone(&self) -> Self {
match self {
#[cfg(not(target_env = "sgx"))]
Storage::Shared(arc) => Storage::Shared(arc.clone()),
Storage::Static => Storage::Static,
Storage::Static(p) => Storage::Static(*p),
}
}
}
Expand Down Expand Up @@ -100,42 +169,23 @@ impl<T: Transmittable> Fifo<T> {
"Fifo len should be a power of two"
);
#[cfg(target_env = "sgx")] {
use std::os::fortanix_sgx::usercalls::alloc::{User, UserRef};

// `fortanix_sgx_abi::WithId` is not `Copy` because it contains an `AtomicU64`.
// This type has the same memory layout but is `Copy` and can be marked as
// `UserSafeSized` which is needed for the `User::from_raw_parts()` below.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct WithId<T> {
pub id: u64,
pub data: T,
}
unsafe impl<T: UserSafeSized> UserSafeSized for WithId<T> {}

unsafe fn _sanity_check_with_id() {
use std::mem::size_of;
let _: [u8; size_of::<fortanix_sgx_abi::WithId<()>>()] = [0u8; size_of::<WithId<()>>()];
}

#[repr(transparent)]
#[derive(Copy, Clone)]
struct WrapUsize(usize);
unsafe impl UserSafeSized for WrapUsize{}
use std::os::fortanix_sgx::usercalls::alloc::User;

// check pointers are outside enclave range, etc.
let data = User::<[WithId<T>]>::from_raw_parts(descriptor.data as _, descriptor.len);
let data = User::<[UserSafeWithId<T>]>::from_raw_parts(descriptor.data as _, descriptor.len);
mem::forget(data);
UserRef::from_ptr(descriptor.offsets as *const WrapUsize);

}
let data_slice = std::slice::from_raw_parts(descriptor.data, descriptor.len);
Self {
data: &*(data_slice as *const [WithId<T>] as *const [UnsafeCell<WithId<T>>]),
offsets: &*descriptor.offsets,
storage: Storage::Static,
storage: Storage::Static(PhantomData::default()),
}
}

#[cfg(not(target_env = "sgx"))]
fn from_arc(fifo: Arc<FifoBuffer<T>>) -> Self {
unsafe {
Self {
Expand All @@ -148,10 +198,11 @@ impl<T: Transmittable> Fifo<T> {

/// Consumes `self` and returns a DescriptorGuard.
/// Panics if `self` was created using `from_descriptor`.
#[cfg(not(target_env = "sgx"))]
pub(crate) fn into_descriptor_guard(self) -> DescriptorGuard<T> {
let arc = match self.storage {
Storage::Shared(arc) => arc,
Storage::Static => panic!("Sender/Receiver created using `from_descriptor()` cannot be turned into DescriptorGuard."),
Storage::Static(_) => panic!("Sender/Receiver created using `from_descriptor()` cannot be turned into DescriptorGuard."),
};
let descriptor = FifoDescriptor {
data: self.data.as_ptr() as _,
Expand Down Expand Up @@ -183,9 +234,11 @@ impl<T: Transmittable> Fifo<T> {
};

// 4. Write the data, then the `id`.
let slot = unsafe { &mut *self.data[new.write_offset()].get() };
slot.data = val.data;
slot.id.store(val.id, Ordering::SeqCst);
unsafe {
let slot = &mut *self.data[new.write_offset()].get();
T::write(&mut slot.data, &val.data);
slot.id.store(val.id, SeqCst);
}

// 5. If the queue was empty in step 1, signal the reader to wake up.
Ok(was_empty)
Expand Down Expand Up @@ -216,8 +269,9 @@ impl<T: Transmittable> Fifo<T> {
};

// 6. Read the data, then store `0` in the `id`.
let val = Identified { id, data: slot.data };
slot.id.store(0, Ordering::SeqCst);
let data = unsafe { T::read(&slot.data) };
let val = Identified { id, data };
slot.id.store(0, SeqCst);

// 7. Store the new read offset, retrieving the old offsets.
let before = fetch_adjust(
Expand Down
2 changes: 2 additions & 0 deletions ipc-queue/src/interface_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl<T: Transmittable, S: AsyncSynchronizer> AsyncSender<T, S> {
/// Consumes `self` and returns a DescriptorGuard.
/// The returned guard can be used to make `FifoDescriptor`s that remain
/// valid as long as the guard is not dropped.
#[cfg(not(target_env = "sgx"))]
pub fn into_descriptor_guard(self) -> DescriptorGuard<T> {
self.inner.into_descriptor_guard()
}
Expand Down Expand Up @@ -82,6 +83,7 @@ impl<T: Transmittable, S: AsyncSynchronizer> AsyncReceiver<T, S> {
/// Consumes `self` and returns a DescriptorGuard.
/// The returned guard can be used to make `FifoDescriptor`s that remain
/// valid as long as the guard is not dropped.
#[cfg(not(target_env = "sgx"))]
pub fn into_descriptor_guard(self) -> DescriptorGuard<T> {
self.inner.into_descriptor_guard()
}
Expand Down
1 change: 1 addition & 0 deletions ipc-queue/src/interface_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ impl<'r, T: Transmittable, S: Synchronizer> Iterator for TryIter<'r, T, S> {

#[cfg(test)]
mod tests {
use crate::fifo::bounded;
use crate::test_support::pubsub::{Channel, Subscription};
use crate::test_support::TestValue;
use crate::*;
Expand Down
44 changes: 36 additions & 8 deletions ipc-queue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@
#![cfg_attr(target_env = "sgx", feature(sgx_platform))]

use std::future::Future;
#[cfg(target_env = "sgx")]
use std::os::fortanix_sgx::usercalls::alloc::UserSafeSized;
use std::pin::Pin;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;

use fortanix_sgx_abi::FifoDescriptor;

use self::fifo::{Fifo, FifoBuffer};
use self::fifo::Fifo;

#[cfg(target_env = "sgx")]
use std::os::fortanix_sgx::usercalls::alloc::{UserRef, UserSafeSized};

#[cfg(not(target_env = "sgx"))]
use {
std::ptr,
self::fifo::FifoBuffer,
};

mod fifo;
mod interface_sync;
Expand All @@ -25,17 +32,36 @@ mod position;
mod test_support;

#[cfg(target_env = "sgx")]
pub trait Transmittable: UserSafeSized + Default {}
pub trait Transmittable: UserSafeSized + Default {
unsafe fn write(ptr: *mut Self, val: &Self) {
UserRef::<Self>::from_mut_ptr(ptr).copy_from_enclave(val)
}

unsafe fn read(ptr: *const Self) -> Self {
let mut data = Default::default();
UserRef::<Self>::from_ptr(ptr).copy_to_enclave(&mut data);
data
}
}

#[cfg(target_env = "sgx")]
impl<T> Transmittable for T where T: UserSafeSized + Default {}

#[cfg(not(target_env = "sgx"))]
pub trait Transmittable: Copy + Sized + Default {}
pub trait Transmittable: Copy + Sized + Default {
unsafe fn write(ptr: *mut Self, val: &Self) {
ptr::write(ptr, *val);
}

unsafe fn read(ptr: *const Self) -> Self {
ptr::read(ptr)
}
}

#[cfg(not(target_env = "sgx"))]
impl<T> Transmittable for T where T: Copy + Sized + Default {}

#[cfg(not(target_env = "sgx"))]
pub fn bounded<T, S>(len: usize, s: S) -> (Sender<T, S>, Receiver<T, S>)
where
T: Transmittable,
Expand All @@ -44,6 +70,7 @@ where
self::fifo::bounded(len, s)
}

#[cfg(not(target_env = "sgx"))]
pub fn bounded_async<T, S>(len: usize, s: S) -> (AsyncSender<T, S>, AsyncReceiver<T, S>)
where
T: Transmittable,
Expand Down Expand Up @@ -125,13 +152,14 @@ pub struct AsyncSender<T: 'static, S> {
pub struct AsyncReceiver<T: 'static, S> {
inner: Fifo<T>,
synchronizer: S,
read_epoch: Arc<AtomicU32>,
read_epoch: Arc<AtomicU64>,
}

/// `DescriptorGuard<T>` can produce a `FifoDescriptor<T>` that is guaranteed
/// to remain valid as long as the DescriptorGuard is not dropped.
pub struct DescriptorGuard<T> {
descriptor: FifoDescriptor<T>,
#[cfg(not(target_env = "sgx"))]
_fifo: Arc<FifoBuffer<T>>,
}

Expand All @@ -147,7 +175,7 @@ impl<T> DescriptorGuard<T> {
/// read to/from the queue. This is useful in case we want to know whether or
/// not a particular value written to the queue has been read.
pub struct PositionMonitor<T: 'static> {
read_epoch: Arc<AtomicU32>,
read_epoch: Arc<AtomicU64>,
fifo: Fifo<T>,
}

Expand Down

0 comments on commit 0837fce

Please sign in to comment.