mc_sgx_io/
lib.rs

1// Copyright (c) 2022-2023 The MobileCoin Foundation
2
3#![doc = include_str!("../README.md")]
4#![deny(missing_docs, missing_debug_implementations)]
5#![no_std]
6
7mod write_buffer;
8
9use core::ffi::c_void;
10use mc_sgx_core_sys_types::sgx_status_t;
11use mc_sgx_core_types::Error;
12use mc_sgx_util::ResultInto;
13pub use write_buffer::WriteBuffer;
14
15/// Write the entire `buffer` into the hosts stderr sink.
16///
17/// # Arguments
18/// * `buffer` - The buffer to write.
19///
20/// # Errors
21/// When not all of `buffer` could be written to the hosts stderr sink.
22///
23/// If there is an error, no assumptions should be made about the amount of
24/// `buffer` that was written.
25pub fn stderr_write_all(buffer: &[u8]) -> Result<(), Error> {
26    unsafe { ocall_stderr(buffer.as_ptr() as *const c_void, buffer.len()) }.into_result()
27}
28
29extern "C" {
30    /// The ocall to send stderr messages to
31    ///
32    /// # Arguments
33    /// * `input` - The input buffer/stream. Should be u8/bytes
34    /// * `len` - The byte length of `input`
35    ///
36    /// # Returns
37    /// `sgx_status_t::SGX_SUCCESS` when all of input was successfully written
38    /// to the untrusted stderr sink.
39    /// An error status if not all of the data could be written to the sink. No
40    /// assumptions are made about how much data was written on error.
41    fn ocall_stderr(input: *const c_void, len: usize) -> sgx_status_t;
42}
43
44// Done out here so that `serial_test` works, since it uses "::std" in the macro
45#[cfg(test)]
46extern crate std;
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use core::slice;
52    use once_cell::sync::Lazy;
53    use serial_test::serial;
54    use std::string::String;
55    use std::sync::Mutex;
56
57    static TEST_STREAM: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
58    static TEST_STREAM_RESULT: Lazy<Mutex<sgx_status_t>> =
59        Lazy::new(|| Mutex::new(sgx_status_t::SGX_SUCCESS));
60
61    fn reset_test_stream() {
62        let mut stream = TEST_STREAM.lock().expect("Mutex has been poisoned");
63        stream.clear();
64        let mut status = TEST_STREAM_RESULT.lock().expect("Mutex has been poisoned");
65        *status = sgx_status_t::SGX_SUCCESS;
66    }
67
68    #[no_mangle]
69    extern "C" fn ocall_stderr(input: *const c_void, len: usize) -> sgx_status_t {
70        let bytes = unsafe { slice::from_raw_parts(input as *const u8, len) };
71        let message =
72            std::str::from_utf8(bytes).expect("Expected valid UTF8 from stderr in enclave");
73        let mut stream = TEST_STREAM.lock().expect("Mutex has been poisoned");
74        stream.clear();
75        stream.push_str(message);
76        let status = TEST_STREAM_RESULT.lock().expect("Mutex has been poisoned");
77        *status
78    }
79
80    #[test]
81    #[serial]
82    fn single_line_output_to_stderr() {
83        reset_test_stream();
84        let test_message = b"what";
85        stderr_write_all(test_message).expect("Expected the write to succeed");
86
87        let written = TEST_STREAM.lock().expect("Mutex has been poisoned");
88        assert_eq!(written.as_bytes(), test_message);
89    }
90
91    #[test]
92    #[serial]
93    fn multi_line_output_to_stderr() {
94        reset_test_stream();
95        let test_message = b"this\nhas\nmultiple\nlines";
96        stderr_write_all(test_message).expect("Expected the write to succeed");
97
98        let written = TEST_STREAM.lock().expect("Mutex has been poisoned");
99        assert_eq!(written.as_bytes(), test_message);
100    }
101
102    #[test]
103    #[serial]
104    fn error_when_outputting_to_stderr() {
105        reset_test_stream();
106        let expected_error = sgx_status_t::SGX_ERROR_FILE_BAD_STATUS;
107        {
108            let mut status = TEST_STREAM_RESULT.lock().expect("Mutex has been poisoned");
109            *status = expected_error;
110        }
111
112        let error = stderr_write_all(b"what").unwrap_err();
113        assert_eq!(error, Error::FileBadStatus);
114    }
115}