From d75d96340c463a473df21f4f6828426e507debf7 Mon Sep 17 00:00:00 2001 From: Nick Santana Date: Wed, 14 Dec 2022 12:40:34 -0800 Subject: [PATCH] Add `mc-sgx-io` Add `mc-sgx-io` which provides functionality for io streams from an SGX enclave --- Cargo.toml | 1 + io/Cargo.toml | 21 ++++++++ io/README.md | 21 ++++++++ io/src/lib.rs | 109 ++++++++++++++++++++++++++++++++++++++++ io/untrusted/Cargo.toml | 1 + io/untrusted/src/lib.rs | 11 ++-- 6 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 io/Cargo.toml create mode 100644 io/README.md create mode 100644 io/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 92c3973..67a2a8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "alloc", + "io", "io/untrusted", ] exclude = [ diff --git a/io/Cargo.toml b/io/Cargo.toml new file mode 100644 index 0000000..f13d36b --- /dev/null +++ b/io/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mc-sgx-io" +version = "0.1.0" +edition = "2021" +authors = ["MobileCoin"] +rust-version = "1.62.1" +license = "Apache-2.0" +readme = "README.md" +repository = "https://github.com/mobilecoinfoundation/sgx-std" +description = "IO implementation for use inside of SGX enclaves" +categories = ["hardware-support", "no-std"] +keywords = ["sgx", "no-std"] + +[dependencies] +mc-sgx-core-sys-types = "0.4.0" +mc-sgx-core-types = "0.4.0" +mc-sgx-util = "0.4.0" + +[dev-dependencies] +once_cell = "1.16.0" +serial_test = "0.9.0" diff --git a/io/README.md b/io/README.md new file mode 100644 index 0000000..fa78a4f --- /dev/null +++ b/io/README.md @@ -0,0 +1,21 @@ +# MobileCoin SGX: IO in Enclave + +[![Project Chat][chat-image]][chat-link]![License][license-image]![Target][target-image][![Crates.io][crate-image]][crate-link][![Docs Status][docs-image]][docs-link][![Dependency Status][deps-image]][deps-link] + +Provide IO streams for use in an SGX enclave + +[chat-image]: https://img.shields.io/discord/844353360348971068?style=flat-square +[chat-link]: https://mobilecoin.chat +[license-image]: https://img.shields.io/crates/l/mc-sgx-io?style=flat-square +[target-image]: https://img.shields.io/badge/target-sgx-red?style=flat-square +[crate-image]: https://img.shields.io/crates/v/mc-sgx-io.svg?style=flat-square +[crate-link]: https://crates.io/crates/mc-sgx-io +[docs-image]: https://img.shields.io/docsrs/mc-sgx-io?style=flat-square +[docs-link]: https://docs.rs/crate/mc-sgx-io +[deps-image]: https://deps.rs/crate/mc-sgx-io/0.1.0/status.svg?style=flat-square +[deps-link]: https://deps.rs/crate/mc-sgx-io/0.1.0 diff --git a/io/src/lib.rs b/io/src/lib.rs new file mode 100644 index 0000000..37cea54 --- /dev/null +++ b/io/src/lib.rs @@ -0,0 +1,109 @@ +// Copyright (c) 2022 The MobileCoin Foundation + +#![doc = include_str!("../README.md")] +#![deny(missing_docs, missing_debug_implementations)] +#![no_std] + +use core::ffi::c_void; +use mc_sgx_core_sys_types::sgx_status_t; +use mc_sgx_core_types::Error; +use mc_sgx_util::ResultInto; + +/// Attempts to write the entire buffer into the hosts stderr sink. +/// +/// # Arguments +/// * `buffer` - The buffer to write. +/// +/// # Errors +/// If there is any error writing all of `buffer` to the sink. No +/// assumptions should be made about the amount that was written to the sink +/// when an error occurs. +pub fn stderr_write_all(buffer: &[u8]) -> Result<(), Error> { + unsafe { ocall_stderr(buffer.as_ptr() as *const c_void, buffer.len()) }.into_result() +} + +extern "C" { + /// The ocall to send stderr messages to + /// + /// # Arguments + /// * `input` - The input buffer/stream. Should be ui8/bytes + /// * `len` - The byte length of `input` + /// + /// # Returns + /// `sgx_status_t::SGX_SUCCESS` when all of input was successfully written + /// to the untrusted stderr sink. + /// An error status if not all of the data could be written to the sink. No + /// assumptions are made about how much data was written on error. + fn ocall_stderr(input: *const c_void, len: usize) -> sgx_status_t; +} + +// Done out here so that `serial_test` works, since it uses "::std" in the macro +#[cfg(test)] +extern crate std; + +#[cfg(test)] +mod tests { + use super::*; + use core::slice; + use once_cell::sync::Lazy; + use serial_test::serial; + use std::string::String; + use std::sync::Mutex; + + static TEST_STREAM: Lazy> = Lazy::new(|| Mutex::new(String::new())); + static TEST_STREAM_RESULT: Lazy> = + Lazy::new(|| Mutex::new(sgx_status_t::SGX_SUCCESS)); + + fn reset_test_stream() { + let mut stream = TEST_STREAM.lock().expect("Mutex has been poisoned"); + stream.clear(); + let mut status = TEST_STREAM_RESULT.lock().expect("Mutex has been poisoned"); + *status = sgx_status_t::SGX_SUCCESS; + } + + #[no_mangle] + extern "C" fn ocall_stderr(input: *const c_void, len: usize) -> sgx_status_t { + let bytes = unsafe { slice::from_raw_parts(input as *const u8, len) }; + let message = + std::str::from_utf8(bytes).expect("Expected valid UTF8 from stderr in enclave"); + let mut stream = TEST_STREAM.lock().expect("Mutex has been poisoned"); + stream.clear(); + stream.push_str(message); + let status = TEST_STREAM_RESULT.lock().expect("Mutex has been poisoned"); + *status + } + + #[test] + #[serial] + fn single_line_output_to_stderr() { + reset_test_stream(); + stderr_write_all(b"what").expect("Expected the write to succeed"); + + let written = TEST_STREAM.lock().expect("Mutex has been poisoned"); + assert_eq!(written.as_str(), "what"); + } + + #[test] + #[serial] + fn multi_line_output_to_stderr() { + reset_test_stream(); + stderr_write_all(b"this\nhas\nmultiple\nlines").expect("Expected the write to succeed"); + + let written = TEST_STREAM.lock().expect("Mutex has been poisoned"); + assert_eq!(written.as_str(), "this\nhas\nmultiple\nlines"); + } + + #[test] + #[serial] + fn error_when_outputting_to_stderr() { + reset_test_stream(); + let expected_error = sgx_status_t::SGX_ERROR_FILE_BAD_STATUS; + { + let mut status = TEST_STREAM_RESULT.lock().expect("Mutex has been poisoned"); + *status = expected_error; + } + + let error = stderr_write_all(b"what").unwrap_err(); + assert_eq!(error, Error::FileBadStatus); + } +} diff --git a/io/untrusted/Cargo.toml b/io/untrusted/Cargo.toml index 0680a3b..abaa762 100644 --- a/io/untrusted/Cargo.toml +++ b/io/untrusted/Cargo.toml @@ -21,4 +21,5 @@ mc-sgx-util = "0.4.0" once_cell = "1.16.0" [dev-dependencies] +serial_test = "0.9.0" test_enclave = { path = "../../test_enclave" } diff --git a/io/untrusted/src/lib.rs b/io/untrusted/src/lib.rs index 5a2a164..28a4b96 100644 --- a/io/untrusted/src/lib.rs +++ b/io/untrusted/src/lib.rs @@ -6,7 +6,7 @@ //! //! By default stderr from an enclave will be directed to the untrusted (host) //! stderr. Consumers can redirect this stream by providing a [`WriteAll`] -//! function via [`stderr_write_all`]. +//! function via [`stderr_sink`]. use once_cell::sync::Lazy; use std::ffi::c_void; @@ -23,7 +23,7 @@ pub type WriteAll = dyn Fn(&[u8]); /// /// # Arguments /// * `write_all` - The function to use for writing stderr from the enclave -pub fn stderr_write_all(write_all: &'static WriteAll) { +pub fn stderr_sink(write_all: &'static WriteAll) { let mut stderr = STDERR.lock().expect("Mutex has been poisoned"); stderr.write_all = write_all; } @@ -68,6 +68,7 @@ mod tests { use super::*; use mc_sgx_urts::EnclaveBuilder; use mc_sgx_util::ResultInto; + use serial_test::serial; use test_enclave::{ecall_round_trip_to_stderr, ENCLAVE}; static TEST_STREAM: Lazy> = Lazy::new(|| Mutex::new(String::new())); @@ -82,8 +83,9 @@ mod tests { } #[test] + #[serial] fn one_line_error_message() { - stderr_write_all(&test_stream_write_all); + stderr_sink(&test_stream_write_all); let enclave = EnclaveBuilder::from(ENCLAVE).create().unwrap(); let id = enclave.id(); @@ -98,8 +100,9 @@ mod tests { } #[test] + #[serial] fn multi_line_error_message() { - stderr_write_all(&test_stream_write_all); + stderr_sink(&test_stream_write_all); let enclave = EnclaveBuilder::from(ENCLAVE).create().unwrap(); let id = enclave.id();