#![deny(missing_docs)]
#![deny(warnings)]
#![allow(clippy::too_many_arguments)]
pub mod commands;
pub mod connection;
pub mod connection_listener;
pub mod cpu_info;
pub mod resource_manager;
pub mod socket;
pub mod utils;
use log::{info, warn};
use nix::sys::epoll::EpollFlags;
use nix::sys::signal::{Signal, SIGHUP};
use nix::unistd::{daemon, getpid, getppid};
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::process;
use std::thread::{self, JoinHandle};
use super::common::MSG_ENCLAVE_CONFIRM;
use super::common::{construct_error_message, enclave_proc_command_send_single, notify_error};
use super::common::{
EnclaveProcessCommandType, ExitGracefully, NitroCliErrorEnum, NitroCliFailure, NitroCliResult,
};
use crate::common::commands_parser::{DescribeEnclavesArgs, EmptyArgs, RunEnclavesArgs};
use crate::common::logger::EnclaveProcLogWriter;
use crate::common::signal_handler::SignalHandler;
use crate::enclave_proc::connection::safe_conn_println;
use crate::new_nitro_cli_failure;
use commands::{describe_enclaves, run_enclaves, terminate_enclaves, DescribeThread};
use connection::Connection;
use connection_listener::ConnectionListener;
use resource_manager::EnclaveManager;
enum HandledEnclaveEvent {
HangUp,
Unexpected,
None,
}
fn get_logger_id(enclave_id: &str) -> String {
let tokens: Vec<_> = enclave_id.rsplit("-enc").collect();
format!("enc-{}:{}", tokens[0], std::process::id())
}
fn get_command_action(cmd: EnclaveProcessCommandType) -> String {
match cmd {
EnclaveProcessCommandType::Run => "Run Enclave".to_string(),
EnclaveProcessCommandType::Terminate | EnclaveProcessCommandType::TerminateComplete => {
"Terminate Enclave".to_string()
}
EnclaveProcessCommandType::Describe => "Describe Enclaves".to_string(),
EnclaveProcessCommandType::GetEnclaveCID => "Get Enclave CID".to_string(),
EnclaveProcessCommandType::GetEnclaveFlags => "Get Enclave Flags".to_string(),
EnclaveProcessCommandType::ConnectionListenerStop => "Stop Connection Listener".to_string(),
_ => "Unknown Command".to_string(),
}
}
fn send_command_and_close(cmd: EnclaveProcessCommandType, stream: &mut UnixStream) {
let action_str = &get_command_action(cmd);
enclave_proc_command_send_single::<EmptyArgs>(cmd, None, stream)
.ok_or_exit_with_errno(Some("Failed to send command"));
stream
.shutdown(std::net::Shutdown::Both)
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to close stream after sending command: {e:?}"),
NitroCliErrorEnum::SocketCloseError
)
.set_action(action_str.to_string())
})
.ok_or_exit_with_errno(Some("Failed to shut down stream"));
}
fn notify_error_with_conn(err_msg: &str, conn: &Connection, action: EnclaveProcessCommandType) {
let action_str = &get_command_action(action);
notify_error(err_msg);
conn.eprintln(err_msg)
.map_err(|e| e.set_action(action_str.to_string()))
.ok_or_exit_with_errno(Some("Failed to forward error message to connection"));
}
fn run_terminate(
connection: Connection,
mut thread_stream: UnixStream,
mut enclave_manager: EnclaveManager,
) {
terminate_enclaves(&mut enclave_manager, Some(&connection)).unwrap_or_else(|e| {
notify_error_with_conn(
construct_error_message(&e).as_str(),
&connection,
EnclaveProcessCommandType::Terminate,
);
});
send_command_and_close(
EnclaveProcessCommandType::TerminateComplete,
&mut thread_stream,
);
}
fn notify_terminate(
connection: Connection,
conn_listener: &ConnectionListener,
enclave_manager: EnclaveManager,
) -> NitroCliResult<JoinHandle<()>> {
let (local_stream, thread_stream) = UnixStream::pair().map_err(|e| {
new_nitro_cli_failure!(
&format!("Could not create stream pair: {e:?}"),
NitroCliErrorEnum::SocketPairCreationFailure
)
})?;
conn_listener.add_stream_to_epoll(local_stream)?;
Ok(thread::spawn(move || {
run_terminate(connection, thread_stream, enclave_manager)
}))
}
fn enclave_proc_configure_signal_handler(conn_listener: &ConnectionListener) -> NitroCliResult<()> {
let mut signal_handler = SignalHandler::new_with_defaults()
.mask_all()
.map_err(|e| e.add_subaction("Failed to configure signal handler".to_string()))?;
let (local_stream, thread_stream) = UnixStream::pair()
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to create stream pair: {e:?}"),
NitroCliErrorEnum::SocketPairCreationFailure
)
.set_action("Run Enclave".to_string())
})
.ok_or_exit_with_errno(Some("Failed to create stream pair"));
conn_listener
.add_stream_to_epoll(local_stream)
.map_err(|e| {
e.add_subaction(
"Failed to add stream to epoll when configuring signal handler".to_string(),
)
})?;
signal_handler.start_handler(thread_stream.into_raw_fd(), enclave_proc_handle_signals);
Ok(())
}
fn enclave_proc_handle_signals(comm_fd: RawFd, signal: Signal) -> bool {
let mut stream = unsafe { UnixStream::from_raw_fd(comm_fd) };
warn!(
"Received signal {:?}. The enclave process will now close.",
signal
);
send_command_and_close(
EnclaveProcessCommandType::ConnectionListenerStop,
&mut stream,
);
true
}
fn try_handle_enclave_event(connection: &Connection) -> NitroCliResult<HandledEnclaveEvent> {
if let Some(mut enc_events) = connection
.get_enclave_event_flags()
.map_err(|e| e.add_subaction("Failed to get enclave events flag".to_string()))?
{
let enc_hup = enc_events.contains(EpollFlags::EPOLLHUP);
enc_events.remove(EpollFlags::EPOLLHUP);
if !enc_events.is_empty() {
warn!("Received unexpected enclave event(s): {:?}", enc_events);
}
if enc_hup {
warn!("Received hang-up event from the enclave. Enclave process will shut down.");
return Ok(HandledEnclaveEvent::HangUp);
}
return Ok(HandledEnclaveEvent::Unexpected);
}
Ok(HandledEnclaveEvent::None)
}
fn fetch_describe_result(
describe_thread: &mut DescribeThread,
enclave_manager: &mut EnclaveManager,
) -> NitroCliResult<()> {
if let Some(thread) = describe_thread.take() {
let result = thread
.join()
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Termination thread join failed: {e:?}"),
NitroCliErrorEnum::ThreadJoinFailure
)
})?
.map_err(|e| e.add_subaction("Failed to save PCR values".to_string()))?;
enclave_manager
.set_measurements(result.measurements)
.map_err(|e| {
e.add_subaction("Failed to set measurements inside enclave handle.".to_string())
})?;
if let Some(metadata) = result.metadata {
enclave_manager.set_metadata(metadata).map_err(|e| {
e.add_subaction("Failed to set metadata inside enclave handle.".to_string())
})?;
}
}
Ok(())
}
fn handle_command(
cmd: EnclaveProcessCommandType,
logger: &EnclaveProcLogWriter,
connection: &Connection,
conn_listener: &mut ConnectionListener,
enclave_manager: &mut EnclaveManager,
terminate_thread: &mut Option<std::thread::JoinHandle<()>>,
describe_thread: &mut DescribeThread,
) -> NitroCliResult<(i32, bool)> {
Ok(match cmd {
EnclaveProcessCommandType::Run => {
if !enclave_manager.enclave_id.is_empty() {
(libc::EEXIST, false)
} else {
let run_args = connection.read::<RunEnclavesArgs>().map_err(|e| {
e.add_subaction("Failed to get run arguments".to_string())
.set_action("Run Enclave".to_string())
})?;
info!("Run args = {:?}", run_args);
let run_result = run_enclaves(&run_args, Some(connection)).map_err(|e| {
e.add_subaction("Failed to trigger enclave run".to_string())
.set_action("Run Enclave".to_string())
})?;
*enclave_manager = run_result.enclave_manager;
*describe_thread = run_result.describe_thread;
info!("Enclave ID = {}", enclave_manager.enclave_id);
logger
.update_logger_id(&get_logger_id(&enclave_manager.enclave_id))
.map_err(|e| e.set_action("Failed to update logger ID".to_string()))?;
conn_listener
.start(&enclave_manager.enclave_id)
.map_err(|e| {
e.set_action("Failed to start connection listener thread".to_string())
})?;
let enc_fd = enclave_manager
.get_enclave_descriptor()
.map_err(|e| e.set_action("Failed to get enclave descriptor".to_string()))?;
conn_listener
.register_enclave_descriptor(enc_fd)
.map_err(|e| {
e.set_action("Failed to register enclave descriptor".to_string())
})?;
(0, false)
}
}
EnclaveProcessCommandType::Terminate => {
*terminate_thread = Some(
notify_terminate(connection.clone(), conn_listener, enclave_manager.clone())
.map_err(|e| {
e.set_action("Failed to send enclave termination request".to_string())
})?,
);
(0, false)
}
EnclaveProcessCommandType::TerminateComplete => {
info!("Enclave has completed termination.");
(0, true)
}
EnclaveProcessCommandType::GetEnclaveCID => {
let enclave_cid = enclave_manager
.get_console_resources_enclave_cid()
.map_err(|e| {
e.set_action("Failed to get console resources (enclave CID)".to_string())
})?;
connection.write_u64(enclave_cid).map_err(|e| {
e.add_subaction("Failed to write enclave CID to connection".to_string())
.set_action("Get Enclave CID".to_string())
})?;
(0, false)
}
EnclaveProcessCommandType::GetEnclaveFlags => {
let enclave_flags = enclave_manager
.get_console_resources_enclave_flags()
.map_err(|e| {
e.set_action("Failed to get console resources (enclave flags)".to_string())
})?;
connection.write_u64(enclave_flags).map_err(|e| {
e.add_subaction("Failed to write enclave flags to connection".to_string())
.set_action("Get Enclave Flags".to_string())
})?;
(0, false)
}
EnclaveProcessCommandType::GetEnclaveName => {
connection.write_u64(MSG_ENCLAVE_CONFIRM).map_err(|e| {
e.add_subaction("Failed to write confirmation".to_string())
.set_action("Get Enclave Name".to_string())
})?;
safe_conn_println(
Some(connection),
serde_json::to_string_pretty(&enclave_manager.enclave_name)
.map_err(|err| {
new_nitro_cli_failure!(
&format!("Failed to write enclave name to connection: {err:?}"),
NitroCliErrorEnum::SerdeError
)
})?
.as_str(),
)?;
(0, false)
}
EnclaveProcessCommandType::GetIDbyName => {
connection.write_u64(MSG_ENCLAVE_CONFIRM).map_err(|e| {
e.add_subaction("Failed to write confirmation".to_string())
.set_action("Name to ID".to_string())
})?;
let name = connection.read::<String>().map_err(|e| {
e.add_subaction("Failed to get enclave name".to_string())
.set_action("Name to ID".to_string())
})?;
if enclave_manager.enclave_name == name {
safe_conn_println(
Some(connection),
serde_json::to_string_pretty(&enclave_manager.enclave_id)
.map_err(|err| {
new_nitro_cli_failure!(
&format!("Failed to display RunEnclaves data: {err:?}"),
NitroCliErrorEnum::SerdeError
)
})?
.as_str(),
)?;
}
(0, false)
}
EnclaveProcessCommandType::Describe => {
let describe_args = connection.read::<DescribeEnclavesArgs>().map_err(|e| {
e.add_subaction("Failed to get describe arguments".to_string())
.set_action("Describe Enclave".to_string())
})?;
connection.write_u64(MSG_ENCLAVE_CONFIRM).map_err(|e| {
e.add_subaction("Failed to write confirmation".to_string())
.set_action("Describe Enclaves".to_string())
})?;
fetch_describe_result(describe_thread, enclave_manager)?;
describe_enclaves(enclave_manager, connection, describe_args.metadata).map_err(
|e| {
e.add_subaction("Failed to describe enclave".to_string())
.set_action("Describe Enclaves".to_string())
},
)?;
(0, false)
}
EnclaveProcessCommandType::ConnectionListenerStop => (0, true),
EnclaveProcessCommandType::NotPermitted => (libc::EACCES, false),
})
}
fn process_event_loop(
comm_stream: UnixStream,
logger: &EnclaveProcLogWriter,
) -> NitroCliResult<()> {
let mut conn_listener = ConnectionListener::new()?;
let mut enclave_manager = EnclaveManager::default();
let mut terminate_thread: Option<std::thread::JoinHandle<()>> = None;
let mut describe_thread: DescribeThread = None;
let mut done = false;
let mut ret_value = Ok(());
enclave_proc_configure_signal_handler(&conn_listener)
.map_err(|e| e.add_subaction("Failed to configure signal handler".to_string()))?;
conn_listener
.handle_new_connection(comm_stream)
.map_err(|e| {
e.add_subaction("Failed to add CLI communication channel to epoll".to_string())
})?;
while !done {
let connection =
conn_listener.get_next_connection(enclave_manager.get_enclave_descriptor().ok())?;
match try_handle_enclave_event(&connection) {
Ok(HandledEnclaveEvent::HangUp) => break,
Ok(HandledEnclaveEvent::Unexpected) => continue,
Ok(HandledEnclaveEvent::None) => (),
Err(error_info) => {
ret_value = Err(error_info
.add_subaction("Error while trying to handle enclave event".to_string()));
break;
}
}
let cmd = match connection.read_command() {
Ok(value) => value,
Err(mut error_info) => {
error_info = error_info
.add_subaction("Failed to read command".to_string())
.set_action("Run Enclave".to_string());
notify_error_with_conn(
&construct_error_message(&error_info),
&connection,
EnclaveProcessCommandType::NotPermitted,
);
break;
}
};
info!("Received command: {:?}", cmd);
let status = handle_command(
cmd,
logger,
&connection,
&mut conn_listener,
&mut enclave_manager,
&mut terminate_thread,
&mut describe_thread,
);
let (status_code, do_break) = match status {
Ok(value) => value,
Err(mut error_info) => {
error_info = error_info
.add_subaction(format!("Failed to execute command `{cmd:?}`"))
.set_action("Run Enclave".to_string());
notify_error_with_conn(&construct_error_message(&error_info), &connection, cmd);
(libc::EINVAL, true)
}
};
done = do_break;
if done {
conn_listener.stop()?;
if terminate_thread.is_some() {
terminate_thread.take().unwrap().join().map_err(|e| {
new_nitro_cli_failure!(
&format!("Termination thread join failed: {e:?}"),
NitroCliErrorEnum::ThreadJoinFailure
)
})?;
};
}
match cmd {
EnclaveProcessCommandType::Run
| EnclaveProcessCommandType::Terminate
| EnclaveProcessCommandType::Describe
| EnclaveProcessCommandType::GetEnclaveName
| EnclaveProcessCommandType::GetIDbyName => {
connection.write_status(status_code).map_err(|_| {
new_nitro_cli_failure!(
"Process event loop failed",
NitroCliErrorEnum::EnclaveProcessSendReplyFailure
)
})?
}
_ => (),
}
}
info!("Enclave process {} exited event loop.", process::id());
ret_value
}
fn create_enclave_process(logger: &EnclaveProcLogWriter) -> NitroCliResult<()> {
let signal_handler = SignalHandler::new(&[SIGHUP])
.mask_all()
.map_err(|e| e.add_subaction("Failed to mask signals".to_string()))?;
let ppid = getpid();
daemon(true, false).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to daemonize enclave process: {e:?}"),
NitroCliErrorEnum::DaemonizeProcessFailure
)
})?;
logger
.update_logger_id(format!("enc-xxxxxxx:{}", std::process::id()).as_str())
.map_err(|e| e.add_subaction("Failed to update logger id".to_string()))?;
info!("Enclave process PID: {}", process::id());
while getppid() == ppid {
thread::sleep(std::time::Duration::from_millis(10));
}
signal_handler
.unmask_all()
.map_err(|e| e.add_subaction("Failed to restore signal handlers".to_string()))?;
Ok(())
}
pub fn enclave_process_run(comm_stream: UnixStream, logger: &EnclaveProcLogWriter) {
create_enclave_process(logger)
.map_err(|e| e.set_action("Run Enclave".to_string()))
.ok_or_exit_with_errno(None);
let res = process_event_loop(comm_stream, logger);
if let Err(mut error_info) = res {
error_info = error_info.set_action("Run Enclave".to_string());
notify_error(construct_error_message(&error_info).as_str());
process::exit(error_info.error_code as i32);
}
process::exit(0);
}