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