#![deny(missing_docs)]
#![deny(warnings)]
use log::{debug, info};
use nix::sys::epoll;
use nix::sys::epoll::{EpollEvent, EpollFlags, EpollOp};
use nix::unistd::*;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::borrow::BorrowMut;
use std::fs;
use std::io::ErrorKind;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use std::os::unix::net::UnixStream;
use crate::common::commands_parser::EmptyArgs;
use crate::common::logger::EnclaveProcLogWriter;
use crate::common::{
enclave_proc_command_send_single, get_socket_path, get_sockets_dir_path, notify_error,
read_u64_le, receive_from_stream,
};
use crate::common::{
EnclaveProcessCommandType, EnclaveProcessReply, NitroCliErrorEnum, NitroCliFailure,
NitroCliResult,
};
use crate::common::{ENCLAVE_PROC_WAIT_TIMEOUT_MSEC, MSG_ENCLAVE_CONFIRM};
use crate::enclave_proc::enclave_process_run;
use crate::new_nitro_cli_failure;
pub fn enclave_proc_spawn(logger: &EnclaveProcLogWriter) -> NitroCliResult<UnixStream> {
let (cli_socket, enclave_proc_socket) = UnixStream::pair().map_err(|e| {
new_nitro_cli_failure!(
&format!("Could not create a socket pair: {e:?}"),
NitroCliErrorEnum::SocketPairCreationFailure
)
})?;
let enclave_proc_fd = enclave_proc_socket.as_raw_fd();
unsafe {
let flags = libc::fcntl(enclave_proc_fd, libc::F_GETFD);
libc::fcntl(enclave_proc_fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC);
}
let fork_status = unsafe { fork() };
if let Ok(ForkResult::Child) = fork_status {
enclave_process_run(enclave_proc_socket, logger);
} else {
fork_status.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to create intermediate process: {e:?}"),
NitroCliErrorEnum::ProcessSpawnFailure
)
})?;
}
Ok(cli_socket)
}
pub fn enclave_proc_connect_to_all() -> NitroCliResult<Vec<UnixStream>> {
let paths = fs::read_dir(get_sockets_dir_path()).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to access sockets directory: {e:?}"),
NitroCliErrorEnum::ReadFromDiskFailure
)
})?;
Ok(paths
.filter_map(|path| path.ok())
.map(|path| path.path())
.filter(|path| !path.is_dir())
.filter_map(|path| {
if let Some(path_str) = path.to_str() {
if !path_str.ends_with(".sock") {
return None;
}
match UnixStream::connect(path_str) {
Ok(conn) => {
info!("Connected to: {}", path_str);
return Some(conn);
}
Err(e) => {
if e.kind() == ErrorKind::PermissionDenied {
info!("Connection to '{}' failed: {}", path_str, e);
} else {
info!("Deleting stale socket: {}", path_str);
let _ = fs::remove_file(path_str).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to delete socket: {e:?}"),
NitroCliErrorEnum::FileOperationFailure
)
.add_info(vec![path_str, "Remove"])
});
}
}
}
}
None
})
.collect())
}
pub fn enclave_proc_connect_to_single(enclave_id: &str) -> NitroCliResult<UnixStream> {
let socket_path = get_socket_path(enclave_id).map_err(|e| {
e.add_subaction("Connect to specific enclave process".to_string())
.set_error_code(NitroCliErrorEnum::SocketError)
})?;
UnixStream::connect(socket_path).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to connect to specific enclave process: {e:?}"),
NitroCliErrorEnum::SocketError
)
})
}
pub fn enclave_proc_command_send_all<T>(
cmd: EnclaveProcessCommandType,
args: Option<&T>,
) -> NitroCliResult<(Vec<UnixStream>, usize)>
where
T: Serialize,
{
let mut replies: Vec<UnixStream> = vec![];
let epoll_fd = epoll::epoll_create().map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to create epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
let comms: Vec<NitroCliResult<()>> = enclave_proc_connect_to_all()
.map_err(|e| {
e.add_subaction("Failed to send command to all enclave processes".to_string())
})?
.into_iter()
.map(|mut socket| {
enclave_proc_command_send_single(cmd, args, socket.borrow_mut())?;
let raw_fd = socket.into_raw_fd();
let mut process_evt = EpollEvent::new(EpollFlags::EPOLLIN, raw_fd as u64);
epoll::epoll_ctl(epoll_fd, EpollOp::EpollCtlAdd, raw_fd, &mut process_evt).map_err(
|e| {
new_nitro_cli_failure!(
&format!("Failed to register socket with epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
},
)?;
Ok(())
})
.collect();
if comms.is_empty() {
return Ok((vec![], 0));
}
let mut num_errors = comms.iter().filter(|result| result.is_err()).count();
let mut num_replies_expected = comms.len() - num_errors;
let mut events = [EpollEvent::empty(); 1];
while num_replies_expected > 0 {
let num_events = loop {
match epoll::epoll_wait(epoll_fd, &mut events[..], ENCLAVE_PROC_WAIT_TIMEOUT_MSEC) {
Ok(num_events) => break num_events,
Err(nix::errno::Errno::EINTR) => continue,
Err(e) => {
return Err(new_nitro_cli_failure!(
&format!("Failed to wait on epoll: {e:?}"),
NitroCliErrorEnum::EpollError
))
}
}
};
num_replies_expected -= 1;
if num_events == 0 {
continue;
}
let input_stream_raw_fd = events[0].data() as RawFd;
let mut input_stream = unsafe { UnixStream::from_raw_fd(input_stream_raw_fd) };
if let Ok(reply) = read_u64_le(&mut input_stream) {
if reply == MSG_ENCLAVE_CONFIRM {
debug!("Got confirmation from {:?}", input_stream);
replies.push(input_stream);
}
}
epoll::epoll_ctl(
epoll_fd,
EpollOp::EpollCtlDel,
input_stream_raw_fd,
Option::None,
)
.map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to remove socket from epoll: {e:?}"),
NitroCliErrorEnum::EpollError
)
})?;
}
num_errors = comms.len() - replies.len();
Ok((replies, num_errors))
}
fn enclave_proc_handle_output<T>(conn: &mut UnixStream) -> (Option<T>, Option<i32>)
where
T: DeserializeOwned,
{
let mut stdout_str = String::new();
let mut status: Option<i32> = None;
while let Ok(reply) = receive_from_stream::<EnclaveProcessReply>(conn) {
match reply {
EnclaveProcessReply::StdOutMessage(msg) => stdout_str.push_str(&msg),
EnclaveProcessReply::StdErrMessage(msg) => eprint!("{msg}"),
EnclaveProcessReply::Status(status_code) => status = Some(status_code),
}
}
match conn.shutdown(std::net::Shutdown::Both) {
Ok(()) => (),
Err(e) => {
notify_error(&format!("Failed to shut connection down: {e}"));
status = Some(-1);
}
}
let json_obj = serde_json::from_str::<T>(&stdout_str).ok();
(json_obj, status)
}
pub fn enclave_proc_handle_outputs<T>(conns: &mut [UnixStream]) -> Vec<(T, i32)>
where
T: DeserializeOwned,
{
let mut objects: Vec<(T, i32)> = Vec::new();
for conn in conns.iter_mut() {
let (object, status) = enclave_proc_handle_output::<T>(conn);
if let Some(object) = object {
if let Some(status) = status {
objects.push((object, status));
}
}
}
objects
}
pub fn enclave_process_handle_all_replies<T>(
replies: &mut [UnixStream],
prev_failed_conns: usize,
print_as_vec: bool,
allowed_return_codes: Vec<i32>,
) -> NitroCliResult<Vec<T>>
where
T: Clone + DeserializeOwned + Serialize,
{
let objects = enclave_proc_handle_outputs::<T>(replies);
let failed_conns = prev_failed_conns + replies.len() - objects.len();
if failed_conns > 0 {
eprintln!("Failed connections: {failed_conns}");
}
if print_as_vec {
let obj_vec: Vec<T> = objects.iter().map(|v| v.0.clone()).collect();
println!(
"{}",
serde_json::to_string_pretty(&obj_vec).map_err(|e| new_nitro_cli_failure!(
&format!("Failed to print JSON vector: {e:?}"),
NitroCliErrorEnum::SerdeError
))?
);
} else {
for object in objects.iter().map(|v| v.0.clone()) {
println!(
"{}",
serde_json::to_string_pretty(&object).map_err(|e| new_nitro_cli_failure!(
&format!("Failed to print JSON object: {e:?}"),
NitroCliErrorEnum::SerdeError
))?
);
}
}
if objects
.iter()
.filter(|v| !allowed_return_codes.contains(&v.1))
.count()
> 0
{
return Err(new_nitro_cli_failure!(
&format!(
"Failed to execute {} enclave process commands",
objects
.iter()
.filter(|v| !allowed_return_codes.contains(&v.1))
.count()
),
NitroCliErrorEnum::EnclaveProcessCommandNotExecuted
));
} else if failed_conns > 0 {
return Err(new_nitro_cli_failure!(
&format!("Failed to connect to {failed_conns} enclave processes"),
NitroCliErrorEnum::EnclaveProcessConnectionFailure
));
}
Ok(objects.into_iter().map(|(o, _)| o).collect())
}
pub fn enclave_proc_get_cid(enclave_id: &str) -> NitroCliResult<u64> {
let mut comm = enclave_proc_connect_to_single(enclave_id)
.map_err(|e| e.add_subaction("Failed to connect to enclave process".to_string()))?;
enclave_proc_command_send_single::<EmptyArgs>(
EnclaveProcessCommandType::GetEnclaveCID,
None,
&mut comm,
)
.map_err(|e| e.add_subaction("Failed to send CID request to enclave process".to_string()))?;
info!("Sent command: GetEnclaveCID");
let enclave_cid = read_u64_le(&mut comm)
.map_err(|e| e.add_subaction(String::from("Failed to read CID from enclave process")))?;
comm.shutdown(std::net::Shutdown::Both).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to shut down connection after obtaining enclave CID: {e:?}"),
NitroCliErrorEnum::SocketError
)
})?;
Ok(enclave_cid)
}
pub fn enclave_proc_get_flags(enclave_id: &str) -> NitroCliResult<u64> {
let mut comm = enclave_proc_connect_to_single(enclave_id)
.map_err(|e| e.add_subaction("Failed to connect to enclave process".to_string()))?;
enclave_proc_command_send_single::<EmptyArgs>(
EnclaveProcessCommandType::GetEnclaveFlags,
None,
&mut comm,
)
.map_err(|e| e.add_subaction("Failed to send flags request to enclave process".to_string()))?;
info!("Sent command: GetEnclaveFlags");
let enclave_flags = read_u64_le(&mut comm)
.map_err(|e| e.add_subaction(String::from("Failed to read flags from enclave process")))?;
comm.shutdown(std::net::Shutdown::Both).map_err(|e| {
new_nitro_cli_failure!(
&format!("Failed to shut down connection after obtaining enclave flags: {e:?}"),
NitroCliErrorEnum::SocketError
)
})?;
Ok(enclave_flags)
}