1#![deny(missing_docs)]
4#![deny(warnings)]
5#![allow(unknown_lints)]
6#![allow(deref_nullptr)]
7
8use aws_nitro_enclaves_image_format::defs::EifIdentityInfo;
9use driver_bindings::*;
10use eif_loader::{enclave_ready, TIMEOUT_MINUTE_MS};
11use libc::c_int;
12use log::{debug, info};
13use std::collections::BTreeMap;
14use std::fs::{File, OpenOptions};
15use std::io::prelude::*;
16use std::io::Error;
17use std::mem::size_of;
18use std::os::unix::io::{AsRawFd, RawFd};
19use std::str;
20use std::sync::{Arc, Mutex};
21use std::time::Duration;
22use vsock::{VsockAddr, VsockListener};
23
24use crate::common::json_output::EnclaveBuildInfo;
25use crate::common::{construct_error_message, notify_error};
26use crate::common::{
27 ExitGracefully, NitroCliErrorEnum, NitroCliFailure, NitroCliResult, ENCLAVE_READY_VSOCK_PORT,
28 VMADDR_CID_PARENT,
29};
30use crate::enclave_proc::connection::Connection;
31use crate::enclave_proc::connection::{safe_conn_eprintln, safe_conn_println};
32use crate::enclave_proc::cpu_info::EnclaveCpuConfig;
33use crate::enclave_proc::utils::get_run_enclaves_info;
34use crate::enclave_proc::utils::{GiB, MiB};
35use crate::new_nitro_cli_failure;
36use crate::utils::ceil_div;
37
38pub type EnclaveStartInfo = ne_enclave_start_info;
40
41pub type UserMemoryRegion = ne_user_memory_region;
43
44pub type ImageLoadInfo = ne_image_load_info;
46
47type UnpackedHandle = (u64, u64, u64, Vec<u32>, u64, u64, EnclaveState);
49
50pub const NE_ENCLAVE_DEBUG_MODE: u64 = 0x1;
52
53const ENCLAVE_MEMORY_EIF_SIZE_RATIO: u64 = 4;
55
56const NE_EIF_IMAGE: u64 = 0x01;
58
59const NE_DEFAULT_MEMORY_REGION: u64 = 0;
61
62const NE_MAGIC: u64 = 0xAE;
64
65const NE_DEV_FILEPATH: &str = "/dev/nitro_enclaves";
67
68pub const NE_CREATE_VM: u64 = nix::request_code_read!(NE_MAGIC, 0x20, size_of::<u64>()) as _;
70
71pub const NE_ADD_VCPU: u64 = nix::request_code_readwrite!(NE_MAGIC, 0x21, size_of::<u32>()) as _;
73
74pub const NE_GET_IMAGE_LOAD_INFO: u64 =
76 nix::request_code_readwrite!(NE_MAGIC, 0x22, size_of::<ImageLoadInfo>()) as _;
77
78pub const NE_SET_USER_MEMORY_REGION: u64 =
80 nix::request_code_write!(NE_MAGIC, 0x23, size_of::<MemoryRegion>()) as _;
81
82pub const NE_START_ENCLAVE: u64 =
84 nix::request_code_readwrite!(NE_MAGIC, 0x24, size_of::<EnclaveStartInfo>()) as _;
85
86const HUGE_PAGE_MAP: [(libc::c_int, u64); 9] = [
88 (libc::MAP_HUGE_16GB, 16 * GiB),
89 (libc::MAP_HUGE_2GB, 2 * GiB),
90 (libc::MAP_HUGE_1GB, GiB),
91 (libc::MAP_HUGE_512MB, 512 * MiB),
92 (libc::MAP_HUGE_256MB, 256 * MiB),
93 (libc::MAP_HUGE_32MB, 32 * MiB),
94 (libc::MAP_HUGE_16MB, 16 * MiB),
95 (libc::MAP_HUGE_8MB, 8 * MiB),
96 (libc::MAP_HUGE_2MB, 2 * MiB),
97];
98
99#[derive(Clone, Debug)]
101pub struct MemoryRegion {
102 flags: u64,
104 mem_size: u64,
106 mem_addr: u64,
108}
109
110#[derive(Clone, Default)]
112pub enum EnclaveState {
113 #[default]
114 Empty,
116 Running,
118 Terminating,
120}
121
122#[derive(Clone, Default)]
124struct ResourceAllocator {
125 requested_mem: u64,
127 mem_regions: Vec<MemoryRegion>,
129}
130
131#[derive(Default)]
133struct EnclaveHandle {
134 #[allow(dead_code)]
136 cpu_config: EnclaveCpuConfig,
137 cpu_ids: Vec<u32>,
139 allocated_memory_mib: u64,
141 slot_uid: u64,
143 enclave_cid: Option<u64>,
145 flags: u64,
147 enc_fd: RawFd,
149 resource_allocator: ResourceAllocator,
151 eif_file: Option<File>,
153 state: EnclaveState,
155 build_info: EnclaveBuildInfo,
157 metadata: Option<EifIdentityInfo>,
159}
160
161#[derive(Clone, Default)]
163pub struct EnclaveManager {
164 pub enclave_id: String,
166 pub enclave_name: String,
168 enclave_handle: Arc<Mutex<EnclaveHandle>>,
170}
171
172impl std::fmt::Display for EnclaveState {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 match self {
175 EnclaveState::Empty => write!(f, "EMPTY"),
176 EnclaveState::Running => write!(f, "RUNNING"),
177 EnclaveState::Terminating => write!(f, "TERMINATING"),
178 }
179 }
180}
181
182impl Default for EnclaveBuildInfo {
183 fn default() -> Self {
184 EnclaveBuildInfo::new(BTreeMap::new())
185 }
186}
187
188impl From<&MemoryRegion> for UserMemoryRegion {
192 fn from(mem_reg: &MemoryRegion) -> UserMemoryRegion {
193 UserMemoryRegion {
194 flags: mem_reg.flags,
195 memory_size: mem_reg.mem_size,
196 userspace_addr: mem_reg.mem_addr,
197 }
198 }
199}
200
201impl MemoryRegion {
202 pub fn new(hugepage_flag: libc::c_int) -> NitroCliResult<Self> {
204 let region_index = HUGE_PAGE_MAP
205 .iter()
206 .position(|&page_info| page_info.0 == hugepage_flag)
207 .ok_or_else(|| {
208 new_nitro_cli_failure!(
209 &format!("Failed to find huge page entry for flag {hugepage_flag:X?}"),
210 NitroCliErrorEnum::NoSuchHugepageFlag
211 )
212 })?;
213 let region_size = HUGE_PAGE_MAP[region_index].1;
214
215 let addr = unsafe {
216 libc::mmap(
217 std::ptr::null_mut(),
218 region_size as usize,
219 libc::PROT_READ | libc::PROT_WRITE,
220 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_HUGETLB | hugepage_flag,
221 -1,
222 0,
223 )
224 };
225
226 if addr == libc::MAP_FAILED {
227 return Err(new_nitro_cli_failure!(
228 "Failed to map memory",
229 NitroCliErrorEnum::EnclaveMmapError
230 ));
231 }
232
233 Ok(MemoryRegion {
235 flags: NE_DEFAULT_MEMORY_REGION,
236 mem_size: region_size,
237 mem_addr: addr as u64,
238 })
239 }
240
241 pub fn new_with(flags: u64, mem_addr: u64, mem_size: u64) -> Self {
243 MemoryRegion {
244 flags,
245 mem_size,
246 mem_addr,
247 }
248 }
249
250 fn free(&mut self) -> NitroCliResult<()> {
252 if self.mem_addr == 0 {
254 return Ok(());
255 }
256
257 let rc =
258 unsafe { libc::munmap(self.mem_addr as *mut libc::c_void, self.mem_size as usize) };
259
260 if rc < 0 {
261 return Err(new_nitro_cli_failure!(
262 "Failed to unmap memory",
263 NitroCliErrorEnum::EnclaveMunmapError
264 ));
265 }
266
267 self.mem_addr = 0;
269 self.mem_size = 0;
270
271 Ok(())
272 }
273
274 fn fill_from_file(
276 &self,
277 file: &mut File,
278 region_offset: usize,
279 size: usize,
280 ) -> NitroCliResult<()> {
281 let offset_plus_size = region_offset.checked_add(size).ok_or_else(|| {
282 new_nitro_cli_failure!(
283 "Memory overflow when writing EIF file to region",
284 NitroCliErrorEnum::MemoryOverflow
285 )
286 })?;
287
288 if offset_plus_size > self.mem_size as usize {
289 return Err(new_nitro_cli_failure!(
290 "Out of region",
291 NitroCliErrorEnum::MemoryOverflow
292 ));
293 }
294
295 let bytes = unsafe {
296 std::slice::from_raw_parts_mut(self.mem_addr as *mut u8, self.mem_size as usize)
297 };
298
299 file.read_exact(&mut bytes[region_offset..region_offset + size])
300 .map_err(|e| {
301 new_nitro_cli_failure!(
302 &format!("Error while reading from enclave image: {e:?}"),
303 NitroCliErrorEnum::EifParsingError
304 )
305 })?;
306
307 Ok(())
308 }
309
310 pub fn mem_addr(&self) -> u64 {
312 self.mem_addr
313 }
314
315 pub fn mem_size(&self) -> u64 {
317 self.mem_size
318 }
319}
320
321impl Drop for MemoryRegion {
322 fn drop(&mut self) {
323 self.free()
324 .ok_or_exit_with_errno(Some("Failed to drop memory region"));
325 }
326}
327
328impl ResourceAllocator {
329 fn new(requested_mem: u64) -> NitroCliResult<Self> {
331 if requested_mem == 0 {
332 return Err(new_nitro_cli_failure!(
333 "Cannot start an enclave with no memory",
334 NitroCliErrorEnum::InsufficientMemoryRequested
335 )
336 .add_info(vec!["memory", &(requested_mem >> 20).to_string()]));
337 }
338
339 Ok(ResourceAllocator {
340 requested_mem,
341 mem_regions: Vec::new(),
342 })
343 }
344
345 fn allocate(&mut self) -> NitroCliResult<&Vec<MemoryRegion>> {
349 let mut allocated_pages = BTreeMap::<u64, u32>::new();
350 let mut needed_mem = self.requested_mem as i64;
351 let mut split_index = 0;
352
353 info!(
354 "Allocating memory regions to hold {} bytes.",
355 self.requested_mem
356 );
357
358 for page_info in HUGE_PAGE_MAP.iter() {
362 while needed_mem >= page_info.1 as i64 {
363 match MemoryRegion::new(page_info.0) {
364 Ok(value) => {
365 needed_mem -= value.mem_size as i64;
366 self.mem_regions.push(value);
367 }
368 Err(_) => break,
369 }
370 }
371 }
372
373 if needed_mem > 0 {
382 for page_info in HUGE_PAGE_MAP.iter().rev() {
383 while needed_mem > 0 {
384 match MemoryRegion::new(page_info.0) {
385 Ok(value) => {
386 needed_mem -= value.mem_size as i64;
387 self.mem_regions.push(value);
388 }
389 Err(_) => break,
390 }
391 }
392 }
393 }
394
395 if needed_mem > 0 {
397 return Err(new_nitro_cli_failure!(
398 &format!(
399 "Failed to allocate entire memory ({} MB remained)",
400 needed_mem >> 20
401 ),
402 NitroCliErrorEnum::InsufficientMemoryAvailable
403 )
404 .add_info(vec!["memory", &(self.requested_mem >> 20).to_string()]));
405 }
406
407 self.mem_regions
410 .sort_by_key(|region| std::cmp::Reverse(region.mem_size));
411
412 needed_mem = self.requested_mem as i64;
413 for region in self.mem_regions.iter() {
414 if needed_mem <= 0 {
415 break;
416 }
417
418 needed_mem -= region.mem_size as i64;
419 split_index += 1
420 }
421
422 self.mem_regions.drain(split_index..);
425
426 for region in self.mem_regions.iter() {
428 if let Some(page_count) = allocated_pages.get_mut(®ion.mem_size) {
429 *page_count += 1;
430 } else {
431 allocated_pages.insert(region.mem_size, 1);
432 }
433 }
434
435 info!(
436 "Allocated {} region(s): {}",
437 self.mem_regions.len(),
438 allocated_pages
439 .iter()
440 .map(|(size, count)| format!("{} page(s) of {} MB", count, size >> 20))
441 .collect::<Vec<String>>()
442 .join(", ")
443 );
444
445 Ok(&self.mem_regions)
446 }
447
448 fn free(&mut self) -> NitroCliResult<()> {
450 for region in self.mem_regions.iter_mut() {
451 region
452 .free()
453 .map_err(|e| e.add_subaction("Failed to free enclave memory region".to_string()))?;
454 }
455
456 self.mem_regions.clear();
457 Ok(())
458 }
459}
460
461impl Drop for ResourceAllocator {
462 fn drop(&mut self) {
463 self.free()
464 .ok_or_exit_with_errno(Some("Failed to drop resource allocator"));
465 }
466}
467
468impl EnclaveHandle {
469 fn new(
471 enclave_cid: Option<u64>,
472 memory_mib: u64,
473 cpu_config: EnclaveCpuConfig,
474 eif_file: File,
475 debug_mode: bool,
476 ) -> NitroCliResult<Self> {
477 let requested_mem = memory_mib << 20;
478 let eif_size = eif_file
479 .metadata()
480 .map_err(|e| {
481 new_nitro_cli_failure!(
482 &format!("Failed to get enclave image file metadata: {e:?}"),
483 NitroCliErrorEnum::FileOperationFailure
484 )
485 })?
486 .len();
487 if ENCLAVE_MEMORY_EIF_SIZE_RATIO * eif_size > requested_mem {
488 return Err(new_nitro_cli_failure!(
489 &format!(
490 "At least {} MB must be allocated (which is {} times the EIF file size)",
491 ceil_div(ceil_div(eif_size, 1024), 1024) * ENCLAVE_MEMORY_EIF_SIZE_RATIO,
492 ENCLAVE_MEMORY_EIF_SIZE_RATIO
493 ),
494 NitroCliErrorEnum::InsufficientMemoryRequested
495 )
496 .add_info(vec![
497 "memory",
498 &memory_mib.to_string(),
499 &(ceil_div(ceil_div(eif_size, 1024), 1024) * ENCLAVE_MEMORY_EIF_SIZE_RATIO)
500 .to_string(),
501 ]));
502 }
503
504 let dev_file = OpenOptions::new()
506 .read(true)
507 .write(true)
508 .open(NE_DEV_FILEPATH)
509 .map_err(|e| {
510 new_nitro_cli_failure!(
511 &format!("Failed to open device file: {e:?}"),
512 NitroCliErrorEnum::FileOperationFailure
513 )
514 .add_info(vec![NE_DEV_FILEPATH, "Open"])
515 })?;
516
517 let mut slot_uid: u64 = 0;
518 let enc_fd = EnclaveHandle::do_ioctl(dev_file.as_raw_fd(), NE_CREATE_VM, &mut slot_uid)
519 .map_err(|e| e.add_subaction("Create VM ioctl failed".to_string()))?;
520 let flags: u64 = if debug_mode { NE_ENCLAVE_DEBUG_MODE } else { 0 };
521
522 if enc_fd < 0 {
523 return Err(new_nitro_cli_failure!(
524 &format!("Invalid enclave file descriptor ({enc_fd})"),
525 NitroCliErrorEnum::InvalidEnclaveFd
526 ));
527 }
528
529 Ok(EnclaveHandle {
530 cpu_config,
531 cpu_ids: vec![],
532 allocated_memory_mib: 0,
533 slot_uid,
534 enclave_cid,
535 flags,
536 enc_fd,
537 resource_allocator: ResourceAllocator::new(requested_mem)
538 .map_err(|e| e.add_subaction("Create resource allocator".to_string()))?,
539 eif_file: Some(eif_file),
540 state: EnclaveState::default(),
541 build_info: EnclaveBuildInfo::new(BTreeMap::new()),
542 metadata: None,
543 })
544 }
545
546 fn create_enclave(
548 &mut self,
549 enclave_name: String,
550 connection: Option<&Connection>,
551 ) -> NitroCliResult<String> {
552 self.init_memory(connection)
553 .map_err(|e| e.add_subaction("Memory initialization issue".to_string()))?;
554 self.init_cpus()
555 .map_err(|e| e.add_subaction("vCPUs initialization issue".to_string()))?;
556
557 let sockaddr = VsockAddr::new(VMADDR_CID_PARENT, ENCLAVE_READY_VSOCK_PORT);
558 let listener = VsockListener::bind(&sockaddr).map_err(|_| {
559 new_nitro_cli_failure!(
560 "Enclave boot heartbeat vsock connection - vsock bind error",
561 NitroCliErrorEnum::EnclaveBootFailure
562 )
563 })?;
564
565 let enclave_start = self
566 .start(connection)
567 .map_err(|e| e.add_subaction("Enclave start issue".to_string()))?;
568
569 let eif_size = self
571 .eif_file
572 .as_ref()
573 .ok_or_else(|| {
574 new_nitro_cli_failure!(
575 "Failed to get EIF file",
576 NitroCliErrorEnum::FileOperationFailure
577 )
578 })?
579 .metadata()
580 .map_err(|e| {
581 new_nitro_cli_failure!(
582 &format!("Failed to get enclave image file metadata: {e:?}"),
583 NitroCliErrorEnum::FileOperationFailure
584 )
585 })?
586 .len();
587
588 let poll_timeout = calculate_necessary_timeout(eif_size, self.allocated_memory_mib * MiB);
590
591 enclave_ready(listener, poll_timeout).map_err(|err| {
592 let err_msg = format!("Waiting on enclave to boot failed with error {err:?}");
593 self.terminate_enclave_error(&err_msg);
594 new_nitro_cli_failure!(&err_msg, NitroCliErrorEnum::EnclaveBootFailure)
595 })?;
596
597 self.enclave_cid = Some(enclave_start.enclave_cid);
598
599 let info = get_run_enclaves_info(
600 enclave_name,
601 enclave_start.enclave_cid,
602 self.slot_uid,
603 self.cpu_ids.clone(),
604 self.allocated_memory_mib,
605 )
606 .map_err(|e| e.add_subaction("Get RunEnclaves information issue".to_string()))?;
607
608 safe_conn_println(
609 connection,
610 serde_json::to_string_pretty(&info)
611 .map_err(|err| {
612 new_nitro_cli_failure!(
613 &format!("Failed to display RunEnclaves data: {err:?}"),
614 NitroCliErrorEnum::SerdeError
615 )
616 })?
617 .as_str(),
618 )?;
619
620 Ok(info.enclave_id)
621 }
622
623 fn init_memory(&mut self, connection: Option<&Connection>) -> NitroCliResult<()> {
625 safe_conn_eprintln(connection, "Start allocating memory...")?;
627
628 let requested_mem_mib = self.resource_allocator.requested_mem >> 20;
629 let regions = self
630 .resource_allocator
631 .allocate()
632 .map_err(|e| e.add_subaction("Failed to allocate enclave memory".to_string()))?;
633
634 self.allocated_memory_mib = regions.iter().fold(0, |mut acc, val| {
635 acc += val.mem_size;
636 acc
637 }) >> 20;
638
639 if self.allocated_memory_mib < requested_mem_mib {
640 return Err(new_nitro_cli_failure!(
641 &format!(
642 "Failed to allocate sufficient memory (requested {} MB, but got {} MB)",
643 requested_mem_mib, self.allocated_memory_mib
644 ),
645 NitroCliErrorEnum::InsufficientMemoryAvailable
646 )
647 .add_info(vec!["memory", &requested_mem_mib.to_string()]));
648 }
649
650 let eif_file = self.eif_file.as_mut().ok_or_else(|| {
651 new_nitro_cli_failure!(
652 "Failed to get mutable reference to EIF file",
653 NitroCliErrorEnum::FileOperationFailure
654 )
655 })?;
656
657 let mut image_load_info = ImageLoadInfo {
658 flags: NE_EIF_IMAGE,
659 memory_offset: 0,
660 };
661 EnclaveHandle::do_ioctl(self.enc_fd, NE_GET_IMAGE_LOAD_INFO, &mut image_load_info)
662 .map_err(|e| e.add_subaction("Get image load info ioctl failed".to_string()))?;
663
664 debug!("Memory load information: {:?}", image_load_info);
665 write_eif_to_regions(eif_file, regions, image_load_info.memory_offset as usize)
666 .map_err(|e| e.add_subaction("Write EIF to enclave memory regions".to_string()))?;
667
668 for region in regions {
670 let mut user_mem_region: UserMemoryRegion = region.into();
671 EnclaveHandle::do_ioctl(self.enc_fd, NE_SET_USER_MEMORY_REGION, &mut user_mem_region)
672 .map_err(|e| e.add_subaction("Set user memory region ioctl failed".to_string()))?;
673 }
674
675 info!("Finished initializing memory.");
676
677 Ok(())
678 }
679
680 fn init_single_cpu(&mut self, mut cpu_id: u32) -> NitroCliResult<()> {
682 EnclaveHandle::do_ioctl(self.enc_fd, NE_ADD_VCPU, &mut cpu_id)
683 .map_err(|e| e.add_subaction("Add vCPU ioctl failed".to_string()))?;
684
685 self.cpu_ids.push(cpu_id);
686 debug!("Added CPU with ID {}.", cpu_id);
687
688 Ok(())
689 }
690
691 fn init_cpus(&mut self) -> NitroCliResult<()> {
693 let cpu_config = self.cpu_config.clone();
694
695 match cpu_config {
696 EnclaveCpuConfig::List(cpu_ids) => {
697 for cpu_id in cpu_ids {
698 self.init_single_cpu(cpu_id).map_err(|e| {
699 e.add_subaction(format!("Failed to add CPU with ID {cpu_id}"))
700 })?;
701 }
702 }
703 EnclaveCpuConfig::Count(cpu_count) => {
704 for _ in 0..cpu_count {
705 self.init_single_cpu(0)?;
706 }
707 }
708 }
709
710 Ok(())
711 }
712
713 fn start(&mut self, connection: Option<&Connection>) -> NitroCliResult<EnclaveStartInfo> {
715 let mut start = EnclaveStartInfo {
716 flags: self.flags,
717 enclave_cid: self.enclave_cid.unwrap_or(0),
718 };
719
720 EnclaveHandle::do_ioctl(self.enc_fd, NE_START_ENCLAVE, &mut start)
721 .map_err(|e| e.add_subaction("Start enclave ioctl failed".to_string()))?;
722
723 safe_conn_eprintln(
724 connection,
725 format!(
726 "Started enclave with enclave-cid: {}, memory: {} MiB, cpu-ids: {:?}",
727 { start.enclave_cid },
728 self.allocated_memory_mib,
729 self.cpu_ids
730 )
731 .as_str(),
732 )?;
733
734 Ok(start)
735 }
736
737 fn terminate_enclave(&mut self) -> NitroCliResult<()> {
739 if self.enclave_cid.unwrap_or(0) != 0 {
740 release_enclave_descriptor(self.enc_fd)
741 .map_err(|e| e.add_subaction("Failed to release enclave descriptor".to_string()))?;
742
743 self.resource_allocator
745 .free()
746 .map_err(|e| e.add_subaction("Failed to release used memory".to_string()))?;
747 info!("Enclave terminated.");
748
749 self.clear();
751 }
752
753 Ok(())
754 }
755
756 fn terminate_enclave_and_notify(&mut self) {
758 if let Err(error_info) = self.terminate_enclave() {
760 let mut err_msg = format!(
761 "Terminating enclave '{:X}' failed with error: {:?}",
762 self.slot_uid,
763 construct_error_message(&error_info).as_str()
764 );
765 err_msg.push_str(
766 "!!! The instance could be in an inconsistent state, please reboot it !!!",
767 );
768
769 notify_error(&err_msg);
771 }
772 }
773
774 fn clear(&mut self) {
776 self.cpu_ids.clear();
777 self.allocated_memory_mib = 0;
778 self.enclave_cid = Some(0);
779 self.enc_fd = -1;
780 self.slot_uid = 0;
781 }
782
783 fn terminate_enclave_error(&mut self, err: &str) {
785 let err_msg = format!("{err}. Terminating the enclave...");
786
787 notify_error(&err_msg);
789 self.terminate_enclave_and_notify();
790 }
791
792 fn do_ioctl<T>(fd: RawFd, ioctl_code: u64, arg: &mut T) -> NitroCliResult<i32> {
794 let rc = unsafe { libc::ioctl(fd, ioctl_code as _, arg) };
795 if rc >= 0 {
796 return Ok(rc);
797 }
798
799 let err_msg = match Error::last_os_error().raw_os_error().unwrap_or(0) as u32 {
800 NE_ERR_VCPU_ALREADY_USED => "The provided vCPU is already used".to_string(),
801 NE_ERR_VCPU_NOT_IN_CPU_POOL => {
802 "The provided vCPU is not available in the CPU pool".to_string()
803 }
804 NE_ERR_VCPU_INVALID_CPU_CORE => {
805 "The vCPU core ID is invalid for the CPU pool".to_string()
806 }
807 NE_ERR_INVALID_MEM_REGION_SIZE => {
808 "The memory region's size is not a multiple of 2 MiB".to_string()
809 }
810 NE_ERR_INVALID_MEM_REGION_ADDR => "The memory region's address is invalid".to_string(),
811 NE_ERR_UNALIGNED_MEM_REGION_ADDR => {
812 "The memory region's address is not aligned".to_string()
813 }
814 NE_ERR_MEM_REGION_ALREADY_USED => "The memory region is already used".to_string(),
815 NE_ERR_MEM_NOT_HUGE_PAGE => {
816 "The memory region is not backed by contiguous physical huge page(s)".to_string()
817 }
818 NE_ERR_MEM_DIFFERENT_NUMA_NODE => {
819 "The memory region's pages and the CPUs belong to different NUMA nodes".to_string()
820 }
821 NE_ERR_MEM_MAX_REGIONS => {
822 "The maximum number of memory regions per enclave has been reached".to_string()
823 }
824 NE_ERR_NO_MEM_REGIONS_ADDED => {
825 "The enclave cannot start because no memory regions have been added".to_string()
826 }
827 NE_ERR_NO_VCPUS_ADDED => {
828 "The enclave cannot start because no vCPUs have been added".to_string()
829 }
830 NE_ERR_ENCLAVE_MEM_MIN_SIZE => {
831 "The enclave's memory size is lower than the minimum supported".to_string()
832 }
833 NE_ERR_FULL_CORES_NOT_USED => {
834 "The enclave cannot start because full CPU cores have not been set".to_string()
835 }
836 NE_ERR_NOT_IN_INIT_STATE => {
837 "The enclave is in an incorrect state to set resources or start".to_string()
838 }
839 NE_ERR_INVALID_VCPU => {
840 "The provided vCPU is out of range of the available CPUs".to_string()
841 }
842 NE_ERR_NO_CPUS_AVAIL_IN_POOL => {
843 "The enclave cannot be created because no CPUs are available in the pool"
844 .to_string()
845 }
846 NE_ERR_INVALID_PAGE_SIZE => {
847 "The memory region is not backed by page(s) multiple of 2 MiB".to_string()
848 }
849 NE_ERR_INVALID_FLAG_VALUE => {
850 "The provided flags value in the ioctl arg data structure is invalid".to_string()
851 }
852 NE_ERR_INVALID_ENCLAVE_CID => {
853 "The provided enclave CID is invalid, being a well-known CID or the parent VM CID"
854 .to_string()
855 }
856 e => format!("An error has occurred: {e} (rc: {rc})"),
857 };
858
859 Err(new_nitro_cli_failure!(
860 &err_msg,
861 NitroCliErrorEnum::IoctlFailure
862 ))
863 }
864}
865
866impl Drop for EnclaveHandle {
867 fn drop(&mut self) {
868 if self.enclave_cid.unwrap_or(0) == 0 {
870 debug!("Resource manager does not hold an enclave.");
871 return;
872 }
873
874 self.terminate_enclave_and_notify();
876 }
877}
878
879impl EnclaveManager {
880 pub fn new(
882 enclave_cid: Option<u64>,
883 memory_mib: u64,
884 cpu_ids: EnclaveCpuConfig,
885 eif_file: File,
886 debug_mode: bool,
887 enclave_name: String,
888 ) -> NitroCliResult<Self> {
889 let enclave_handle =
890 EnclaveHandle::new(enclave_cid, memory_mib, cpu_ids, eif_file, debug_mode)
891 .map_err(|e| e.add_subaction("Failed to create enclave handle".to_string()))?;
892 Ok(EnclaveManager {
893 enclave_id: String::new(),
894 enclave_name,
895 enclave_handle: Arc::new(Mutex::new(enclave_handle)),
896 })
897 }
898
899 pub fn run_enclave(&mut self, connection: Option<&Connection>) -> NitroCliResult<()> {
904 self.enclave_id = self
905 .enclave_handle
906 .lock()
907 .map_err(|e| {
908 new_nitro_cli_failure!(
909 &format!("Failed to acquire lock: {e:?}"),
910 NitroCliErrorEnum::LockAcquireFailure
911 )
912 })?
913 .create_enclave(self.enclave_name.clone(), connection)
914 .map_err(|e| e.add_subaction("Failed to create enclave".to_string()))?;
915 Ok(())
916 }
917
918 pub fn set_measurements(
920 &mut self,
921 measurements: BTreeMap<String, String>,
922 ) -> NitroCliResult<()> {
923 self.enclave_handle
924 .lock()
925 .map_err(|e| {
926 new_nitro_cli_failure!(
927 &format!("Failed to acquire lock: {e:?}"),
928 NitroCliErrorEnum::LockAcquireFailure
929 )
930 })?
931 .build_info = EnclaveBuildInfo::new(measurements);
932 Ok(())
933 }
934
935 pub fn set_metadata(&mut self, metadata: EifIdentityInfo) -> NitroCliResult<()> {
937 self.enclave_handle
938 .lock()
939 .map_err(|e| {
940 new_nitro_cli_failure!(
941 &format!("Failed to acquire lock: {e:?}"),
942 NitroCliErrorEnum::LockAcquireFailure
943 )
944 })?
945 .metadata = Some(metadata);
946 Ok(())
947 }
948
949 pub fn get_description_resources(&self) -> NitroCliResult<UnpackedHandle> {
953 let locked_handle = self.enclave_handle.lock().map_err(|e| {
954 new_nitro_cli_failure!(
955 &format!("Failed to acquire lock: {e:?}"),
956 NitroCliErrorEnum::LockAcquireFailure
957 )
958 })?;
959 Ok((
960 locked_handle.slot_uid,
961 locked_handle.enclave_cid.unwrap(),
962 locked_handle.cpu_ids.len() as u64,
963 locked_handle.cpu_ids.clone(),
964 locked_handle.allocated_memory_mib,
965 locked_handle.flags,
966 locked_handle.state.clone(),
967 ))
968 }
969
970 pub fn get_measurements(&self) -> NitroCliResult<EnclaveBuildInfo> {
972 let locked_handle = self.enclave_handle.lock().map_err(|e| {
973 new_nitro_cli_failure!(
974 &format!("Failed to acquire lock: {e:?}"),
975 NitroCliErrorEnum::LockAcquireFailure
976 )
977 })?;
978 Ok(locked_handle.build_info.clone())
979 }
980
981 pub fn get_metadata(&self) -> NitroCliResult<Option<EifIdentityInfo>> {
983 let locked_handle = self.enclave_handle.lock().map_err(|e| {
984 new_nitro_cli_failure!(
985 &format!("Failed to acquire lock: {e:?}"),
986 NitroCliErrorEnum::LockAcquireFailure
987 )
988 })?;
989 Ok(locked_handle.metadata.clone())
990 }
991
992 pub fn get_console_resources_enclave_cid(&self) -> NitroCliResult<u64> {
996 let locked_handle = self.enclave_handle.lock().map_err(|e| {
997 new_nitro_cli_failure!(
998 &format!("Failed to acquire lock: {e:?}"),
999 NitroCliErrorEnum::LockAcquireFailure
1000 )
1001 })?;
1002 Ok(locked_handle.enclave_cid.unwrap())
1003 }
1004
1005 pub fn get_console_resources_enclave_flags(&self) -> NitroCliResult<u64> {
1009 let locked_handle = self.enclave_handle.lock().map_err(|e| {
1010 new_nitro_cli_failure!(
1011 &format!("Failed to acquire lock: {e:?}"),
1012 NitroCliErrorEnum::LockAcquireFailure
1013 )
1014 })?;
1015 Ok(locked_handle.flags)
1016 }
1017
1018 fn get_termination_resources(&self) -> NitroCliResult<(RawFd, ResourceAllocator)> {
1022 let locked_handle = self.enclave_handle.lock().map_err(|e| {
1023 new_nitro_cli_failure!(
1024 &format!("Failed to acquire lock: {e:?}"),
1025 NitroCliErrorEnum::LockAcquireFailure
1026 )
1027 })?;
1028 Ok((
1029 locked_handle.enc_fd,
1030 locked_handle.resource_allocator.clone(),
1031 ))
1032 }
1033
1034 pub fn get_enclave_descriptor(&self) -> NitroCliResult<RawFd> {
1038 let locked_handle = self.enclave_handle.lock().map_err(|e| {
1039 new_nitro_cli_failure!(
1040 &format!("Failed to acquire lock: {e:?}"),
1041 NitroCliErrorEnum::LockAcquireFailure
1042 )
1043 })?;
1044 Ok(locked_handle.enc_fd)
1045 }
1046
1047 pub fn update_state(&mut self, state: EnclaveState) -> NitroCliResult<()> {
1051 let mut locked_handle = self.enclave_handle.lock().map_err(|e| {
1052 new_nitro_cli_failure!(
1053 &format!("Failed to acquire lock: {e:?}"),
1054 NitroCliErrorEnum::LockAcquireFailure
1055 )
1056 })?;
1057 locked_handle.state = state;
1058 Ok(())
1059 }
1060
1061 pub fn terminate_enclave(&mut self) -> NitroCliResult<()> {
1067 let (enc_fd, mut resource_allocator) = self.get_termination_resources().map_err(|e| {
1068 e.add_subaction("Enclave manager failed to get termination resources".to_string())
1069 })?;
1070 release_enclave_descriptor(enc_fd).map_err(|e| {
1071 e.add_subaction("Enclave manager failed to release enclave descriptor".to_string())
1072 })?;
1073 resource_allocator.free().map_err(|e| {
1074 e.add_subaction("Enclave manager failed to free enclave memory".to_string())
1075 })?;
1076 self.enclave_handle
1077 .lock()
1078 .map_err(|e| {
1079 new_nitro_cli_failure!(
1080 &format!("Failed to acquire lock: {e:?}"),
1081 NitroCliErrorEnum::LockAcquireFailure
1082 )
1083 })?
1084 .clear();
1085 Ok(())
1086 }
1087}
1088
1089fn write_eif_to_regions(
1091 eif_file: &mut File,
1092 regions: &[MemoryRegion],
1093 image_write_offset: usize,
1094) -> NitroCliResult<()> {
1095 let file_size = eif_file
1096 .metadata()
1097 .map_err(|_| {
1098 new_nitro_cli_failure!(
1099 "Failed to obtain EIF file metadata",
1100 NitroCliErrorEnum::FileOperationFailure
1101 )
1102 })?
1103 .len() as usize;
1104
1105 eif_file.rewind().map_err(|_| {
1106 new_nitro_cli_failure!(
1107 "Failed to seek to the beginning of the EIF file",
1108 NitroCliErrorEnum::FileOperationFailure
1109 )
1110 })?;
1111
1112 let mut total_written: usize = 0;
1113
1114 for region in regions {
1115 let offset_plus_file_size = file_size.checked_add(image_write_offset).ok_or_else(|| {
1116 new_nitro_cli_failure!(
1117 "Memory overflow when trying to write EIF file",
1118 NitroCliErrorEnum::MemoryOverflow
1119 )
1120 })?;
1121
1122 if total_written >= offset_plus_file_size {
1123 break;
1125 }
1126
1127 let written_plus_region_size = total_written
1128 .checked_add(region.mem_size as usize)
1129 .ok_or_else(|| {
1130 new_nitro_cli_failure!(
1131 "Memory overflow when trying to write EIF file",
1132 NitroCliErrorEnum::MemoryOverflow
1133 )
1134 })?;
1135
1136 if written_plus_region_size <= image_write_offset {
1137 } else {
1139 let region_offset = image_write_offset.saturating_sub(total_written);
1140 let file_offset = total_written.saturating_sub(image_write_offset);
1141 let size = std::cmp::min(
1142 region.mem_size as usize - region_offset,
1143 file_size - file_offset,
1144 );
1145 region
1146 .fill_from_file(eif_file, region_offset, size)
1147 .map_err(|e| {
1148 e.add_subaction("Failed to fill region with file content".to_string())
1149 })?;
1150 }
1151 total_written += region.mem_size as usize;
1152 }
1153
1154 Ok(())
1155}
1156
1157fn release_enclave_descriptor(enc_fd: RawFd) -> NitroCliResult<()> {
1159 let rc = unsafe { libc::close(enc_fd) };
1161 if rc < 0 {
1162 return Err(new_nitro_cli_failure!(
1163 "Failed to close enclave descriptor",
1164 NitroCliErrorEnum::FileOperationFailure
1165 ));
1166 }
1167
1168 Ok(())
1169}
1170
1171pub fn between_packets_delay() -> Option<Duration> {
1174 if let Ok(value) = std::env::var("NITRO_BETWEEN_PACKETS_MILLIS") {
1175 if let Ok(value) = value.parse::<u64>() {
1176 return Some(Duration::from_millis(value));
1177 }
1178 }
1179
1180 None
1181}
1182
1183pub fn calculate_necessary_timeout(eif_size: u64, allocated_memory: u64) -> c_int {
1199 let eif_size_timeout: c_int =
1201 ((1 + (eif_size - 1) / (6 * GiB)) as i32).saturating_mul(TIMEOUT_MINUTE_MS);
1202
1203 let allocated_memory_timeout: c_int =
1205 ((1 + (allocated_memory - 1) / (100 * GiB)) as i32).saturating_mul(TIMEOUT_MINUTE_MS);
1206
1207 eif_size_timeout + allocated_memory_timeout
1208}
1209
1210#[cfg(test)]
1211mod tests {
1212 use super::calculate_necessary_timeout;
1213 use crate::enclave_proc::utils::GiB;
1214 use eif_loader::TIMEOUT_MINUTE_MS;
1215
1216 #[test]
1217 fn test_timeout_calculation() {
1218 assert_eq!(
1219 calculate_necessary_timeout(2 * GiB, 32 * GiB),
1220 2 * TIMEOUT_MINUTE_MS
1221 );
1222 assert_eq!(
1223 calculate_necessary_timeout(6 * GiB, 32 * GiB),
1224 2 * TIMEOUT_MINUTE_MS
1225 );
1226 assert_eq!(
1227 calculate_necessary_timeout(10 * GiB, 32 * GiB),
1228 3 * TIMEOUT_MINUTE_MS
1229 );
1230
1231 assert_eq!(
1232 calculate_necessary_timeout(2 * GiB, 128 * GiB),
1233 3 * TIMEOUT_MINUTE_MS
1234 );
1235 assert_eq!(
1236 calculate_necessary_timeout(6 * GiB, 128 * GiB),
1237 3 * TIMEOUT_MINUTE_MS
1238 );
1239 assert_eq!(
1240 calculate_necessary_timeout(10 * GiB, 128 * GiB),
1241 4 * TIMEOUT_MINUTE_MS
1242 );
1243 }
1244}