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