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