Skip to content
Open
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
21 changes: 0 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions litebox/src/platform/arch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

//! Architecture-specific platform interfaces.
//!
//! As it currently stands, the interfaces here are only considered for x86-64, in the future other
//! architectures might be supported.

use thiserror::Error;

/// A provider of architecture-specific functionality.
#[cfg(target_arch = "x86_64")]
pub trait ArchSpecificProvider {
/// Get the architecture-specific `reg`, for the current context
fn get_arch_specific_register(
&self,
reg: &ArchSpecificRegister,
) -> Result<usize, ArchSpecificError>;

/// Set the architecture-specific `reg` to `val`, for the current context
fn set_arch_specific_register(
&self,
reg: &ArchSpecificRegister,
val: usize,
) -> Result<(), ArchSpecificError>;
}

/// Architecture-specific registers.
///
/// Implementations of [`ArchSpecificProvider`] can choose to support any subset of these registers,
/// and are not required to support any of them, although this may (unsurprisingly) lead to reduced
/// functionality of certain shims.
#[cfg(target_arch = "x86_64")]
#[non_exhaustive]
pub enum ArchSpecificRegister {
FsBase,
GsBase,
}

/// Errors that can be produced by a [`ArchSpecificProvider`] operation.
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ArchSpecificError {
#[error("register is (currently) not supported on the platform")]
RegisterUnsupported,
#[error("register is reserved by the platform and access is not allowed")]
RegisterReserved,
}
18 changes: 12 additions & 6 deletions litebox/src/platform/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,19 @@ impl TimeProvider for MockPlatform {
}
}

impl PunchthroughProvider for MockPlatform {
type PunchthroughToken<'a> = trivial_providers::ImpossiblePunchthroughToken;
fn get_punchthrough_token_for<'a>(
impl ArchSpecificProvider for MockPlatform {
fn get_arch_specific_register(
&self,
punchthrough: <Self::PunchthroughToken<'a> as PunchthroughToken>::Punchthrough,
) -> Option<Self::PunchthroughToken<'a>> {
None
_reg: &ArchSpecificRegister,
) -> Result<usize, ArchSpecificError> {
Err(ArchSpecificError::RegisterUnsupported)
}
fn set_arch_specific_register(
&self,
_reg: &ArchSpecificRegister,
_val: usize,
) -> Result<(), ArchSpecificError> {
Err(ArchSpecificError::RegisterUnsupported)
}
}

Expand Down
134 changes: 3 additions & 131 deletions litebox/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
//! trait is merely a collection of subtraits that could be composed independently from various
//! other crates that implement them upon various types.

mod arch;
pub mod common_providers;
pub mod page_mgmt;
pub mod trivial_providers;

#[cfg(test)]
pub(crate) mod mock;

use either::Either;
use thiserror::Error;
use zerocopy::{FromBytes, IntoBytes};

pub use arch::{ArchSpecificError, ArchSpecificProvider, ArchSpecificRegister};
pub use page_mgmt::PageManagementProvider;

/// A provider of a platform upon which LiteBox can execute.
Expand All @@ -26,7 +27,7 @@ pub use page_mgmt::PageManagementProvider;
/// provided by it. _However_, most of the provided APIs within the provider act upon an `&self` to
/// allow storage of any useful "globals" within it necessary.
pub trait Provider:
RawMutexProvider + IPInterfaceProvider + TimeProvider + PunchthroughProvider + RawPointerProvider
RawMutexProvider + IPInterfaceProvider + TimeProvider + ArchSpecificProvider + RawPointerProvider
{
}

Expand Down Expand Up @@ -144,126 +145,6 @@ pub trait SignalProvider {
fn take_pending_signals(&self, f: impl FnMut(Self::Signal)) {}
}

/// Punch through any functionality for a particular platform that is not explicitly part of the
/// common _shared_ platform interface.
///
/// The punchthrough primarily exists to improve auditability, rather than preventing arbitrary
/// calls outside of the common interface, since it is impossible in Rust to prevent arbitrary
/// external calls. Thus, it should not be thought of as a security boundary. However, this should
/// be treated closer to "if someone is invoking things from the host without passing through a
/// punchthrough, their code is suspicious; if all host invocations pass through the punchthrough,
/// then it is sufficient to audit the punchthrough interface".
pub trait PunchthroughProvider {
type PunchthroughToken<'a>: PunchthroughToken;
/// Give permission token to invoke `punchthrough`, possibly after checking that it is ok.
///
/// Even though `&self` is taken shared, the intention with the tokens is to use them
/// _immediately_ before invoking other platform interactions. Ideally, we would ensure this via
/// an `&mut self` to guarantee exclusivity, but this would limit us from supporting the ability
/// for other threads being blocked when a punchthrough is done. Thus, this is kept as a
/// `&self`. Morally this should be viewed as a `&mut self`.
fn get_punchthrough_token_for<'a>(
&self,
punchthrough: <Self::PunchthroughToken<'a> as PunchthroughToken>::Punchthrough,
) -> Option<Self::PunchthroughToken<'a>>;
}

/// A token that demonstrates that the platform is allowing access for a particular [`Punchthrough`]
/// to occur (at that point, or at some indeterminate point in the future).
pub trait PunchthroughToken {
type Punchthrough: Punchthrough;
/// Consume the token, and invoke the underlying punchthrough that it represented.
fn execute(
self,
) -> Result<
<Self::Punchthrough as Punchthrough>::ReturnSuccess,
PunchthroughError<<Self::Punchthrough as Punchthrough>::ReturnFailure>,
>;
}

/// Punchthrough support allowing access to functionality not captured by [`Provider`].
///
/// Ideally, this is implemented by a (possibly `#[non_exhaustive]`) enum where a platform
/// provider can mark any unsupported/unimplemented punchthrough functionality with a
/// [`PunchthroughError::Unsupported`] or [`PunchthroughError::Unimplemented`].
///
/// The `Token` allows for obtaining permission from (and possibly, mutable access to) the platform
pub trait Punchthrough {
type ReturnSuccess;
type ReturnFailure: core::error::Error;
}

/// Possible errors for a [`Punchthrough`]
#[derive(Error, Debug)]
pub enum PunchthroughError<E: core::error::Error> {
#[error("attempted to execute unsupported punchthrough")]
Unsupported,
#[error("punchthrough for `{0}` is not implemented")]
Unimplemented(&'static str),
#[error(transparent)]
Failure(#[from] E),
}

/// An error-implementing [`Either`]-style type.
#[derive(Error, Debug)]
pub enum EitherError<L: core::error::Error, R: core::error::Error> {
#[error(transparent)]
Left(L),
#[error(transparent)]
Right(R),
}

// To support easily composing punchthroughs, it is implemented on the `Either` type on
// punchthroughs. An implementation of punchthrough could follow a similar implementation to
// obtain easy internal composability, but composing across crates providing punchthroughs is
// likely best provided using this `Either` based composition.
impl<L, R> PunchthroughToken for Either<L, R>
where
L: PunchthroughToken,
R: PunchthroughToken,
{
type Punchthrough = Either<L::Punchthrough, R::Punchthrough>;

fn execute(
self,
) -> Result<
<Self::Punchthrough as Punchthrough>::ReturnSuccess,
PunchthroughError<<Self::Punchthrough as Punchthrough>::ReturnFailure>,
> {
match self {
Either::Left(l) => match l.execute() {
Ok(res) => Ok(Either::Left(res)),
Err(PunchthroughError::Unsupported) => Err(PunchthroughError::Unsupported),
Err(PunchthroughError::Unimplemented(e)) => {
Err(PunchthroughError::Unimplemented(e))
}
Err(PunchthroughError::Failure(e)) => {
Err(PunchthroughError::Failure(EitherError::Left(e)))
}
},
Either::Right(r) => match r.execute() {
Ok(res) => Ok(Either::Right(res)),
Err(PunchthroughError::Unsupported) => Err(PunchthroughError::Unsupported),
Err(PunchthroughError::Unimplemented(e)) => {
Err(PunchthroughError::Unimplemented(e))
}
Err(PunchthroughError::Failure(e)) => {
Err(PunchthroughError::Failure(EitherError::Right(e)))
}
},
}
}
}

impl<L, R> Punchthrough for Either<L, R>
where
L: Punchthrough,
R: Punchthrough,
{
type ReturnSuccess = Either<L::ReturnSuccess, R::ReturnSuccess>;
type ReturnFailure = EitherError<L::ReturnFailure, R::ReturnFailure>;
}

/// A provider of raw mutexes
pub trait RawMutexProvider {
type RawMutex: RawMutex;
Expand Down Expand Up @@ -669,15 +550,6 @@ pub unsafe trait ThreadLocalStorageProvider {
/// This can be achieved by using
/// [`shim_thread_local!`](crate::shim_thread_local).
unsafe fn replace_thread_local_storage(value: *mut ()) -> *mut ();

/// Clear any guest thread-local storage state for the current thread.
///
/// This is used to help emulate certain syscalls (e.g., `execve`) that clear TLS.
///
/// TODO: move this to a separate trait or eliminate.
fn clear_guest_thread_local_storage() {
unimplemented!()
}
}

/// A provider of cryptographically-secure random data.
Expand Down
98 changes: 1 addition & 97 deletions litebox/src/platform/trivial_providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
//! Most users of LiteBox may possibly need more featureful providers, provided by other crates;
//! however, some users might find these sufficient for their use case.

use super::{
Punchthrough, PunchthroughError, PunchthroughProvider, PunchthroughToken, RawConstPointer,
RawMutPointer, TimerHandle,
};
use super::{RawConstPointer, RawMutPointer, TimerHandle};

use zerocopy::{FromBytes, IntoBytes};

Expand All @@ -22,99 +19,6 @@ impl TimerHandle for UnsupportedTimerHandle {
}
}

/// A trivial provider, useful when no punchthrough is necessary.
pub struct ImpossiblePunchthroughProvider {}
impl PunchthroughProvider for ImpossiblePunchthroughProvider {
type PunchthroughToken<'a> = ImpossiblePunchthroughToken;
fn get_punchthrough_token_for<'a>(
&self,
_punchthrough: <Self::PunchthroughToken<'a> as PunchthroughToken>::Punchthrough,
) -> Option<Self::PunchthroughToken<'a>> {
// Since `ImpossiblePunchthrough` is an empty enum, it is impossible to actually invoke
// `execute` upon it, meaning that the implementation here is irrelevant, since anything
// within it is provably unreachable.
unreachable!()
}
}
/// A [`Punchthrough`] for [`ImpossiblePunchthroughProvider`]
pub enum ImpossiblePunchthrough {}
impl Punchthrough for ImpossiblePunchthrough {
// Infallible has the same role as the never type (`!`) which will _eventually_ be stabilized in
// Rust. Since `Infallible` has no variant, a value of this type can never actually exist.
type ReturnSuccess = core::convert::Infallible;
type ReturnFailure = core::convert::Infallible;
}
/// A [`PunchthroughToken`] for [`ImpossiblePunchthrough`]
pub enum ImpossiblePunchthroughToken {}
impl PunchthroughToken for ImpossiblePunchthroughToken {
type Punchthrough = ImpossiblePunchthrough;
fn execute(
self,
) -> Result<
<Self::Punchthrough as Punchthrough>::ReturnSuccess,
PunchthroughError<<Self::Punchthrough as Punchthrough>::ReturnFailure>,
> {
// Since `ImpossiblePunchthroughToken` is an empty enum, it is impossible to actually invoke
// `execute` upon it, meaning that the implementation here is irrelevant, since anything
// within it is provably unreachable.
unreachable!()
}
}

/// A trivial provider, useful when punchthroughs are be necessary, but might prefer to be
/// simply caught as "unimplemented" temporarily, while more infrastructure is set up.
pub struct IgnoredPunchthroughProvider {}
impl PunchthroughProvider for IgnoredPunchthroughProvider {
type PunchthroughToken<'a> = IgnoredPunchthroughToken;
fn get_punchthrough_token_for<'a>(
&self,
punchthrough: <Self::PunchthroughToken<'a> as PunchthroughToken>::Punchthrough,
) -> Option<Self::PunchthroughToken<'a>> {
Some(IgnoredPunchthroughToken { punchthrough })
}
}
/// A [`Punchthrough`] for [`IgnoredPunchthroughProvider`]
pub struct IgnoredPunchthrough {
data: &'static str,
}
impl Punchthrough for IgnoredPunchthrough {
type ReturnSuccess = Underspecified;
type ReturnFailure = Underspecified;
}
/// A [`PunchthroughToken`] for [`IgnoredPunchthrough`]
pub struct IgnoredPunchthroughToken {
punchthrough: IgnoredPunchthrough,
}
impl PunchthroughToken for IgnoredPunchthroughToken {
type Punchthrough = IgnoredPunchthrough;
fn execute(
self,
) -> Result<
<Self::Punchthrough as Punchthrough>::ReturnSuccess,
PunchthroughError<<Self::Punchthrough as Punchthrough>::ReturnFailure>,
> {
Err(PunchthroughError::Unimplemented(self.punchthrough.data))
}
}

/// An under-specified type that cannot be "inspected" or created; used for [`IgnoredPunchthrough`]
#[doc(hidden)]
pub struct Underspecified {
// Explicitly private field, to prevent destructuring or creation outside this module.
__private: (),
}
impl core::fmt::Debug for Underspecified {
fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
unreachable!("Underspecified is never constructed")
}
}
impl core::fmt::Display for Underspecified {
fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
unreachable!("Underspecified is never constructed")
}
}
impl core::error::Error for Underspecified {}

/// A trivial [`RawConstPointer`] that is literally just `*const T`.
///
/// Useful for purely-userland contexts.
Expand Down
Loading
Loading