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