xref: /cloud-hypervisor/vmm/src/igvm/igvm_loader.rs (revision adb318f4cd0079246b3cb07e01c4e978330445d2)
1 // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
2 //
3 // Copyright © 2023, Microsoft Corporation
4 //
5 use crate::cpu::CpuManager;
6 use zerocopy::AsBytes;
7 
8 use crate::igvm::loader::Loader;
9 use crate::igvm::IgvmLoadedInfo;
10 use crate::igvm::{BootPageAcceptance, StartupMemoryType, HV_PAGE_SIZE};
11 use crate::memory_manager::MemoryManager;
12 use igvm_defs::IgvmPageDataType;
13 use igvm_defs::IgvmPlatformType;
14 use igvm_parser::IgvmDirectiveHeader;
15 use igvm_parser::IgvmFile;
16 use igvm_parser::IgvmPlatformHeader;
17 use igvm_parser::IsolationType;
18 
19 use igvm_defs::IGVM_VHS_PARAMETER;
20 use igvm_defs::IGVM_VHS_PARAMETER_INSERT;
21 
22 use igvm_parser::snp_defs::SevVmsa;
23 pub use mshv_bindings::*;
24 use std::collections::HashMap;
25 use std::ffi::CString;
26 use std::io::Read;
27 use std::io::Seek;
28 use std::io::SeekFrom;
29 use std::mem::size_of;
30 use std::sync::{Arc, Mutex};
31 use thiserror::Error;
32 
33 #[cfg(feature = "sev_snp")]
34 use crate::GuestMemoryMmap;
35 #[cfg(feature = "sev_snp")]
36 use igvm_defs::{MemoryMapEntryType, IGVM_VHS_MEMORY_MAP_ENTRY};
37 
38 #[derive(Debug, Error)]
39 pub enum Error {
40     #[error("command line is not a valid C string")]
41     InvalidCommandLine(#[source] std::ffi::NulError),
42     #[error("failed to read igvm file")]
43     Igvm(#[source] std::io::Error),
44     #[error("invalid igvm file")]
45     InvalidIgvmFile(#[source] igvm_parser::Error),
46     #[error("invalid guest memory map")]
47     InvalidGuestMemmap(#[source] arch::Error),
48     #[error("loader error")]
49     Loader(#[source] crate::igvm::loader::Error),
50     #[error("parameter too large for parameter area")]
51     ParameterTooLarge,
52     #[error("Error importing isolated pages: {0}")]
53     ImportIsolatedPages(#[source] hypervisor::HypervisorVmError),
54     #[error("Error completing importing isolated pages: {0}")]
55     CompleteIsolatedImport(#[source] hypervisor::HypervisorVmError),
56 }
57 
58 #[allow(dead_code)]
59 #[derive(Copy, Clone)]
60 struct GpaPages {
61     pub gpa: u64,
62     pub page_type: u32,
63     pub page_size: u32,
64 }
65 
66 #[derive(Debug)]
67 enum ParameterAreaState {
68     /// Parameter area has been declared via a ParameterArea header.
69     Allocated { data: Vec<u8>, max_size: u64 },
70     /// Parameter area inserted and invalid to use.
71     Inserted,
72 }
73 
74 #[cfg(feature = "sev_snp")]
75 fn igvm_memmap_from_ram_range(ram_range: (u64, u64)) -> IGVM_VHS_MEMORY_MAP_ENTRY {
76     assert!(ram_range.0 % HV_PAGE_SIZE == 0);
77     assert!((ram_range.1 - ram_range.0) % HV_PAGE_SIZE == 0);
78 
79     IGVM_VHS_MEMORY_MAP_ENTRY {
80         starting_gpa_page_number: ram_range.0 / HV_PAGE_SIZE,
81         number_of_pages: (ram_range.1 - ram_range.0) / HV_PAGE_SIZE,
82         entry_type: MemoryMapEntryType::MEMORY,
83         flags: 0,
84         reserved: 0,
85     }
86 }
87 
88 #[cfg(feature = "sev_snp")]
89 fn generate_memory_map(
90     guest_mem: &GuestMemoryMmap,
91 ) -> Result<Vec<IGVM_VHS_MEMORY_MAP_ENTRY>, Error> {
92     let mut memory_map = Vec::new();
93 
94     // Get usable physical memory ranges
95     let ram_ranges = arch::generate_ram_ranges(guest_mem).map_err(Error::InvalidGuestMemmap)?;
96 
97     for ram_range in ram_ranges {
98         memory_map.push(igvm_memmap_from_ram_range(ram_range));
99     }
100 
101     Ok(memory_map)
102 }
103 
104 // Import a parameter to the given parameter area.
105 fn import_parameter(
106     parameter_areas: &mut HashMap<u32, ParameterAreaState>,
107     info: &IGVM_VHS_PARAMETER,
108     parameter: &[u8],
109 ) -> Result<(), Error> {
110     let (parameter_area, max_size) = match parameter_areas
111         .get_mut(&info.parameter_area_index)
112         .expect("parameter area should be present")
113     {
114         ParameterAreaState::Allocated { data, max_size } => (data, max_size),
115         ParameterAreaState::Inserted => panic!("igvmfile is not valid"),
116     };
117     let offset = info.byte_offset as usize;
118     let end_of_parameter = offset + parameter.len();
119 
120     if end_of_parameter > *max_size as usize {
121         // TODO: tracing for which parameter was too big?
122         return Err(Error::ParameterTooLarge);
123     }
124 
125     if parameter_area.len() < end_of_parameter {
126         parameter_area.resize(end_of_parameter, 0);
127     }
128 
129     parameter_area[offset..end_of_parameter].copy_from_slice(parameter);
130     Ok(())
131 }
132 
133 ///
134 /// Load the given IGVM file to guest memory.
135 /// Right now it only supports SNP based isolation.
136 /// We can boot legacy VM with an igvm file without
137 /// any isolation.
138 ///
139 pub fn load_igvm(
140     mut file: &std::fs::File,
141     memory_manager: Arc<Mutex<MemoryManager>>,
142     cpu_manager: Arc<Mutex<CpuManager>>,
143     cmdline: &str,
144 ) -> Result<Box<IgvmLoadedInfo>, Error> {
145     let mut loaded_info: Box<IgvmLoadedInfo> = Box::default();
146     let command_line = CString::new(cmdline).map_err(Error::InvalidCommandLine)?;
147     let mut file_contents = Vec::new();
148     let memory = memory_manager.lock().as_ref().unwrap().guest_memory();
149     let mut gpas: Vec<GpaPages> = Vec::new();
150     let proc_count = cpu_manager.lock().unwrap().vcpus().len() as u32;
151 
152     file.seek(SeekFrom::Start(0)).map_err(Error::Igvm)?;
153     file.read_to_end(&mut file_contents).map_err(Error::Igvm)?;
154 
155     let igvm_file = IgvmFile::new_from_binary(&file_contents, Some(IsolationType::Snp))
156         .map_err(Error::InvalidIgvmFile)?;
157 
158     let mask = match &igvm_file.platforms()[0] {
159         IgvmPlatformHeader::SupportedPlatform(info) => {
160             debug_assert!(info.platform_type == IgvmPlatformType::SEV_SNP);
161             info.compatibility_mask
162         }
163     };
164 
165     let mut loader = Loader::new(memory);
166 
167     let mut parameter_areas: HashMap<u32, ParameterAreaState> = HashMap::new();
168 
169     for header in igvm_file.directives() {
170         debug_assert!(header.compatibility_mask().unwrap_or(mask) & mask == mask);
171 
172         match header {
173             IgvmDirectiveHeader::PageData {
174                 gpa,
175                 compatibility_mask: _,
176                 flags,
177                 data_type,
178                 data,
179             } => {
180                 debug_assert!(data.len() as u64 % HV_PAGE_SIZE == 0);
181 
182                 // TODO: only 4k or empty page data supported right now
183                 assert!(data.len() as u64 == HV_PAGE_SIZE || data.is_empty());
184 
185                 let acceptance = match *data_type {
186                     IgvmPageDataType::NORMAL => {
187                         if flags.unmeasured() {
188                             gpas.push(GpaPages {
189                                 gpa: *gpa,
190                                 page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED,
191                                 page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
192                             });
193                             BootPageAcceptance::ExclusiveUnmeasured
194                         } else {
195                             gpas.push(GpaPages {
196                                 gpa: *gpa,
197                                 page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL,
198                                 page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
199                             });
200                             BootPageAcceptance::Exclusive
201                         }
202                     }
203                     IgvmPageDataType::SECRETS => {
204                         gpas.push(GpaPages {
205                             gpa: *gpa,
206                             page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_SECRETS,
207                             page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
208                         });
209                         BootPageAcceptance::SecretsPage
210                     }
211                     IgvmPageDataType::CPUID_DATA => {
212                         // SAFETY: CPUID is readonly
213                         unsafe {
214                             let cpuid_page_p: *mut hv_psp_cpuid_page =
215                                 data.as_ptr() as *mut hv_psp_cpuid_page; // as *mut hv_psp_cpuid_page;
216                             let cpuid_page: &mut hv_psp_cpuid_page = &mut *cpuid_page_p;
217                             for i in 0..cpuid_page.count {
218                                 let leaf = cpuid_page.cpuid_leaf_info[i as usize];
219                                 let mut in_leaf = cpu_manager
220                                     .lock()
221                                     .unwrap()
222                                     .get_cpuid_leaf(
223                                         0,
224                                         leaf.eax_in,
225                                         leaf.ecx_in,
226                                         leaf.xfem_in,
227                                         leaf.xss_in,
228                                     )
229                                     .unwrap();
230                                 if leaf.eax_in == 1 {
231                                     in_leaf[2] &= 0x7FFFFFFF;
232                                 }
233                                 cpuid_page.cpuid_leaf_info[i as usize].eax_out = in_leaf[0];
234                                 cpuid_page.cpuid_leaf_info[i as usize].ebx_out = in_leaf[1];
235                                 cpuid_page.cpuid_leaf_info[i as usize].ecx_out = in_leaf[2];
236                                 cpuid_page.cpuid_leaf_info[i as usize].edx_out = in_leaf[3];
237                             }
238                         }
239                         gpas.push(GpaPages {
240                             gpa: *gpa,
241                             page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_CPUID,
242                             page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
243                         });
244                         BootPageAcceptance::CpuidPage
245                     }
246                     // TODO: other data types SNP / TDX only, unsupported
247                     _ => todo!("unsupported IgvmPageDataType"),
248                 };
249 
250                 loader
251                     .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data)
252                     .map_err(Error::Loader)?;
253             }
254             IgvmDirectiveHeader::ParameterArea {
255                 number_of_bytes,
256                 parameter_area_index,
257                 initial_data,
258             } => {
259                 debug_assert!(number_of_bytes % HV_PAGE_SIZE == 0);
260                 debug_assert!(
261                     initial_data.is_empty() || initial_data.len() as u64 == *number_of_bytes
262                 );
263 
264                 // Allocate a new parameter area. It must not be already used.
265                 if parameter_areas
266                     .insert(
267                         *parameter_area_index,
268                         ParameterAreaState::Allocated {
269                             data: initial_data.clone(),
270                             max_size: *number_of_bytes,
271                         },
272                     )
273                     .is_some()
274                 {
275                     panic!("IgvmFile is not valid, invalid invariant");
276                 }
277             }
278             IgvmDirectiveHeader::VpCount(info) => {
279                 import_parameter(&mut parameter_areas, info, proc_count.as_bytes())?;
280             }
281             IgvmDirectiveHeader::MmioRanges(_info) => {
282                 todo!("unsupported IgvmPageDataType");
283             }
284             IgvmDirectiveHeader::MemoryMap(_info) => {
285                 #[cfg(feature = "sev_snp")]
286                 {
287                     let guest_mem = memory_manager.lock().unwrap().boot_guest_memory();
288                     let memory_map = generate_memory_map(&guest_mem)?;
289                     import_parameter(&mut parameter_areas, _info, memory_map.as_bytes())?;
290                 }
291 
292                 #[cfg(not(feature = "sev_snp"))]
293                 todo!("Not implemented");
294             }
295             IgvmDirectiveHeader::CommandLine(info) => {
296                 import_parameter(&mut parameter_areas, info, command_line.as_bytes_with_nul())?;
297             }
298             IgvmDirectiveHeader::RequiredMemory {
299                 gpa,
300                 compatibility_mask: _,
301                 number_of_bytes,
302                 vtl2_protectable: _,
303             } => {
304                 let memory_type = StartupMemoryType::Ram;
305                 loaded_info.gpas.push(*gpa);
306                 loader
307                     .verify_startup_memory_available(
308                         gpa / HV_PAGE_SIZE,
309                         *number_of_bytes as u64 / HV_PAGE_SIZE,
310                         memory_type,
311                     )
312                     .map_err(Error::Loader)?;
313             }
314             IgvmDirectiveHeader::SnpVpContext {
315                 gpa,
316                 compatibility_mask: _,
317                 vp_index,
318                 vmsa,
319             } => {
320                 assert_eq!(gpa % HV_PAGE_SIZE, 0);
321                 let mut data: [u8; 4096] = [0; 4096];
322                 let len = size_of::<SevVmsa>();
323                 loaded_info.vmsa_gpa = *gpa;
324                 loaded_info.vmsa = **vmsa;
325                 // Only supported for index zero
326                 if *vp_index == 0 {
327                     data[..len].copy_from_slice(vmsa.as_bytes());
328                     loader
329                         .import_pages(gpa / HV_PAGE_SIZE, 1, BootPageAcceptance::VpContext, &data)
330                         .map_err(Error::Loader)?;
331                 }
332 
333                 gpas.push(GpaPages {
334                     gpa: *gpa,
335                     page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_VMSA,
336                     page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
337                 });
338             }
339             IgvmDirectiveHeader::SnpIdBlock {
340                 compatibility_mask,
341                 author_key_enabled,
342                 reserved,
343                 ld,
344                 family_id,
345                 image_id,
346                 version,
347                 guest_svn,
348                 id_key_algorithm,
349                 author_key_algorithm,
350                 id_key_signature,
351                 id_public_key,
352                 author_key_signature,
353                 author_public_key,
354             } => {
355                 loaded_info.snp_id_block.compatibility_mask = *compatibility_mask;
356                 loaded_info.snp_id_block.author_key_enabled = *author_key_enabled;
357                 loaded_info.snp_id_block.reserved = *reserved;
358                 loaded_info.snp_id_block.ld = *ld;
359                 loaded_info.snp_id_block.family_id = *family_id;
360                 loaded_info.snp_id_block.image_id = *image_id;
361                 loaded_info.snp_id_block.version = *version;
362                 loaded_info.snp_id_block.guest_svn = *guest_svn;
363                 loaded_info.snp_id_block.id_key_algorithm = *id_key_algorithm;
364                 loaded_info.snp_id_block.author_key_algorithm = *author_key_algorithm;
365                 loaded_info.snp_id_block.id_key_signature = **id_key_signature;
366                 loaded_info.snp_id_block.id_public_key = **id_public_key;
367                 loaded_info.snp_id_block.author_key_signature = **author_key_signature;
368                 loaded_info.snp_id_block.author_public_key = **author_public_key;
369             }
370             IgvmDirectiveHeader::X64VbsVpContext {
371                 vtl: _,
372                 registers: _,
373                 compatibility_mask: _,
374             } => {
375                 todo!("VbsVpContext not supported");
376             }
377             IgvmDirectiveHeader::VbsMeasurement { .. } => {
378                 todo!("VbsMeasurement not supported")
379             }
380             IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT {
381                 gpa,
382                 compatibility_mask: _,
383                 parameter_area_index,
384             }) => {
385                 debug_assert!(gpa % HV_PAGE_SIZE == 0);
386 
387                 let area = parameter_areas
388                     .get_mut(parameter_area_index)
389                     .expect("igvmfile should be valid");
390                 match area {
391                     ParameterAreaState::Allocated { data, max_size } => loader
392                         .import_pages(
393                             gpa / HV_PAGE_SIZE,
394                             *max_size / HV_PAGE_SIZE,
395                             BootPageAcceptance::ExclusiveUnmeasured,
396                             data,
397                         )
398                         .map_err(Error::Loader)?,
399                     ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"),
400                 }
401                 *area = ParameterAreaState::Inserted;
402                 gpas.push(GpaPages {
403                     gpa: *gpa,
404                     page_type: hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED,
405                     page_size: hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
406                 });
407             }
408             IgvmDirectiveHeader::ErrorRange { .. } => {
409                 todo!("Error Range not supported")
410             }
411             _ => {
412                 todo!("Header not supported!!")
413             }
414         }
415     }
416 
417     #[cfg(feature = "sev_snp")]
418     {
419         use std::time::Instant;
420 
421         let mut now = Instant::now();
422 
423         // Sort the gpas to group them by the page type
424         gpas.sort_by(|a, b| a.gpa.cmp(&b.gpa));
425 
426         let gpas_grouped = gpas
427             .iter()
428             .fold(Vec::<Vec<GpaPages>>::new(), |mut acc, gpa| {
429                 if let Some(last_vec) = acc.last_mut() {
430                     if last_vec[0].page_type == gpa.page_type {
431                         last_vec.push(*gpa);
432                         return acc;
433                     }
434                 }
435                 acc.push(vec![*gpa]);
436                 acc
437             });
438 
439         // Import the pages as a group(by page type) of PFNs to reduce the
440         // hypercall.
441         for group in gpas_grouped.iter() {
442             info!(
443                 "Importing {} page{}",
444                 group.len(),
445                 if group.len() > 1 { "s" } else { "" }
446             );
447             // Convert the gpa into PFN as MSHV hypercall takes an array
448             // of PFN for importing the isolated pages
449             let pfns: Vec<u64> = group
450                 .iter()
451                 .map(|gpa| gpa.gpa >> HV_HYP_PAGE_SHIFT)
452                 .collect();
453             memory_manager
454                 .lock()
455                 .unwrap()
456                 .vm
457                 .import_isolated_pages(
458                     group[0].page_type,
459                     hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
460                     &pfns,
461                 )
462                 .map_err(Error::ImportIsolatedPages)?;
463         }
464 
465         info!(
466             "Time it took to for hashing pages {:.2?} and page_count {:?}",
467             now.elapsed(),
468             gpas.len()
469         );
470 
471         now = Instant::now();
472         // Call Complete Isolated Import since we are done importing isolated pages
473         let host_data: [u8; 32] = [0; 32];
474         memory_manager
475             .lock()
476             .unwrap()
477             .vm
478             .complete_isolated_import(loaded_info.snp_id_block, host_data, 1)
479             .map_err(Error::CompleteIsolatedImport)?;
480 
481         info!(
482             "Time it took to for launch complete command  {:.2?}",
483             now.elapsed()
484         );
485     }
486 
487     debug!("Dumping the contents of VMSA page: {:x?}", loaded_info.vmsa);
488     Ok(loaded_info)
489 }
490