nitro_enclaves/launch/
linux.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use super::{error::*, types::*};
4
5use std::{
6    cmp::min,
7    fs::File,
8    io::{Read, Seek},
9};
10
11pub const NE_MAGIC: u64 = 0xAE;
12
13// Create a slot that is associated with an enclave VM.
14pub const NE_CREATE_VM: u64 = nix::request_code_read!(NE_MAGIC, 0x20, size_of::<u64>()) as _;
15
16// Set a vCPU for an enclave.
17pub const NE_ADD_VCPU: u64 = nix::request_code_readwrite!(NE_MAGIC, 0x21, size_of::<u32>()) as _;
18
19// Get information needed for in-memory enclave image loading.
20pub const NE_GET_IMAGE_LOAD_INFO: u64 =
21    nix::request_code_readwrite!(NE_MAGIC, 0x22, size_of::<ImageLoadInfo>()) as _;
22
23// Set an enclave's memory region.
24pub const NE_SET_USER_MEMORY_REGION: u64 =
25    nix::request_code_write!(NE_MAGIC, 0x23, size_of::<UserMemoryRegion>()) as _;
26
27// Start running an enclave.
28pub const NE_START_ENCLAVE: u64 =
29    nix::request_code_readwrite!(NE_MAGIC, 0x24, size_of::<StartInfo>()) as _;
30
31// Default enclave memory region.
32const NE_DEFAULT_MEMORY_REGION: u64 = 0;
33
34const HUGE_FLAG_SIZE: [(libc::c_int, usize); 9] = [
35    (libc::MAP_HUGE_16GB, 16 << 30),
36    (libc::MAP_HUGE_2GB, 2 << 30),
37    (libc::MAP_HUGE_1GB, 1 << 30),
38    (libc::MAP_HUGE_512MB, 512 << 20),
39    (libc::MAP_HUGE_256MB, 256 << 20),
40    (libc::MAP_HUGE_32MB, 32 << 20),
41    (libc::MAP_HUGE_16MB, 16 << 20),
42    (libc::MAP_HUGE_8MB, 8 << 20),
43    (libc::MAP_HUGE_2MB, 2 << 20),
44];
45
46/// Info necessary for in-memory enclave image.
47#[derive(Debug, Default)]
48#[repr(C)]
49pub struct ImageLoadInfo {
50    /// Flags to determine the enclave image type (e.g. Enclave Image Format [EIF]).
51    flags: u64,
52
53    /// Offset in enclave memory where to start placing the enclave image.
54    pub memory_offset: u64,
55}
56
57impl From<&ImageType<'_>> for ImageLoadInfo {
58    fn from(image_type: &ImageType) -> Self {
59        let flags = match image_type {
60            ImageType::Eif(_) => 0x01,
61        };
62
63        Self {
64            flags,
65            ..Default::default()
66        }
67    }
68}
69
70/// Enclave memory region.
71#[derive(Debug, Default)]
72#[repr(C)]
73pub struct UserMemoryRegion {
74    /// Usage flags.
75    pub flags: u64,
76
77    /// Region size (in bytes).
78    pub size: u64,
79
80    /// Userspace (virtual) address of region.
81    pub uaddr: u64,
82}
83
84impl UserMemoryRegion {
85    pub fn image_fill(
86        &mut self,
87        image: &mut File,
88        offset: usize,
89        image_size: usize,
90        written: &mut usize,
91    ) -> Result<(), MemInitError> {
92        let Some(location) = written.checked_add(self.size as usize) else {
93            return Err(MemInitError::OffsetCheckOverflow);
94        };
95
96        if location > offset {
97            // Calculate the offset both within the memory region and within the image file to
98            // begin reading from and writing to.
99            let region_offset = offset.saturating_sub(*written);
100            let image_offset = written.saturating_sub(offset);
101
102            // Amount to write is restricted by both the memory region's capacity and amount of
103            // data remaining in the image file.
104            let write_amount = min(
105                self.size as usize - region_offset,
106                image_size - image_offset,
107            );
108
109            let bytes = unsafe {
110                std::slice::from_raw_parts_mut(self.uaddr as *mut u8, self.size as usize)
111            };
112
113            image
114                .read_exact(&mut bytes[region_offset..region_offset + write_amount])
115                .map_err(MemInitError::ImageRead)?;
116        }
117
118        *written += self.size as usize;
119
120        Ok(())
121    }
122}
123
124/// Allocated enclave memory regions.
125pub struct UserMemoryRegions(Vec<UserMemoryRegion>);
126
127impl UserMemoryRegions {
128    /// Allocate huge pages for enclave memory from the requested size (in MiB).
129    pub fn new(size_mib: usize) -> Result<Self, MemInitError> {
130        let mut regions = Vec::new();
131        let mut size = size_mib << 20;
132        let mut found: bool;
133
134        while size > 0 {
135            found = false;
136            for (hp_flag, reg_size) in HUGE_FLAG_SIZE {
137                // Prevent wasting memory by only allocating huge pages that are smaller in size
138                // than the remaining memory needing to be allocated.
139                if size < reg_size {
140                    continue;
141                }
142
143                let addr = unsafe {
144                    libc::mmap(
145                        std::ptr::null_mut(),
146                        reg_size,
147                        libc::PROT_READ | libc::PROT_WRITE,
148                        libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_HUGETLB | hp_flag,
149                        -1,
150                        0,
151                    )
152                };
153
154                if addr == libc::MAP_FAILED {
155                    continue;
156                }
157
158                let region = UserMemoryRegion {
159                    flags: NE_DEFAULT_MEMORY_REGION,
160                    size: reg_size as _,
161                    uaddr: addr as _,
162                };
163
164                regions.push(region);
165                size -= reg_size;
166                found = true;
167            }
168
169            // Ensure that a valid memory region was found and mapped. If not, return an error.
170            if !found {
171                return Err(MemInitError::NoHugePageFound);
172            }
173        }
174
175        Ok(Self(regions))
176    }
177
178    /// Populate the memory regions with the enclave image.
179    pub fn image_fill(&mut self, offset: usize, image: ImageType) -> Result<(), MemInitError> {
180        // Only EIF images are supported at the moment.
181        let ImageType::Eif(image) = image;
182
183        // Get the size of the enclave image.
184        let metadata = image.metadata().map_err(MemInitError::ImageMetadata)?;
185        let image_size = metadata.len() as usize;
186        image.rewind().map_err(MemInitError::ImageRewind)?;
187
188        // Calculate the final index in guest memory in which the image should be written.
189        let Some(limit) = offset.checked_add(image_size) else {
190            return Err(MemInitError::ImagePlacementOverflow);
191        };
192
193        // Write the enclave image to the memory regions.
194        let mut written: usize = 0;
195        for region in &mut self.0 {
196            region.image_fill(image, offset, image_size, &mut written)?;
197            if written >= limit {
198                break;
199            }
200        }
201
202        // Ensure the entire enclave image was written.
203        if written < limit {
204            return Err(MemInitError::ImageWriteIncomplete);
205        }
206
207        Ok(())
208    }
209
210    /// Get a reference to the inner vector of memory regions.
211    pub fn inner_ref(&self) -> &Vec<UserMemoryRegion> {
212        &self.0
213    }
214}
215
216/// Encapsulates info for startting an enclave.
217#[repr(C)]
218pub struct StartInfo {
219    /// Usage flags.
220    flags: u64,
221
222    /// Enclave CID.
223    pub cid: u64,
224}
225
226impl StartInfo {
227    pub fn new(flags: StartFlags, cid: u64) -> Self {
228        let flags = flags.bits();
229
230        Self { flags, cid }
231    }
232}