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