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