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