diff --git a/igvm/src/lib.rs b/igvm/src/lib.rs index 9a52865..e619a6d 100644 --- a/igvm/src/lib.rs +++ b/igvm/src/lib.rs @@ -64,6 +64,8 @@ pub enum IsolationType { Sev, /// This guest is isolated with SEV-ES (physical or emulated). SevEs, + /// This guest is isolated with Cca (physical or emulated). + Cca, } impl From for igvm_defs::IgvmPlatformType { @@ -75,6 +77,7 @@ impl From for igvm_defs::IgvmPlatformType { IsolationType::Tdx => IgvmPlatformType::TDX, IsolationType::Sev => IgvmPlatformType::SEV, IsolationType::SevEs => IgvmPlatformType::SEV_ES, + IsolationType::Cca => IgvmPlatformType::CCA, } } } @@ -262,6 +265,11 @@ impl IgvmPlatformHeader { } // TODO: shared gpa boundary req? } + IgvmPlatformType::CCA => { + if info.platform_version != IGVM_CCA_PLATFORM_VERSION { + return Err(BinaryHeaderError::InvalidPlatformVersion); + } + } _ => { return Err(BinaryHeaderError::InvalidPlatformType); } @@ -725,6 +733,11 @@ pub enum IgvmDirectiveHeader { registers: Vec, compatibility_mask: u32, }, + AArch64CcaVpContext { + compatibility_mask: u32, + vp_index: u16, + context: Box, + }, ParameterInsert(IGVM_VHS_PARAMETER_INSERT), ErrorRange { gpa: u64, @@ -970,6 +983,7 @@ impl IgvmDirectiveHeader { IgvmDirectiveHeader::X64NativeVpContext { .. } => size_of::(), IgvmDirectiveHeader::X64VbsVpContext { .. } => size_of::(), IgvmDirectiveHeader::AArch64VbsVpContext { .. } => size_of::(), + IgvmDirectiveHeader::AArch64CcaVpContext { .. } => size_of::(), IgvmDirectiveHeader::ParameterInsert(param) => size_of_val(param), IgvmDirectiveHeader::ErrorRange { .. } => size_of::(), IgvmDirectiveHeader::SnpIdBlock { .. } => size_of::(), @@ -1010,6 +1024,9 @@ impl IgvmDirectiveHeader { IgvmDirectiveHeader::AArch64VbsVpContext { .. } => { IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT } + IgvmDirectiveHeader::AArch64CcaVpContext { .. } => { + IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT + } IgvmDirectiveHeader::ParameterInsert(_) => { IgvmVariableHeaderType::IGVM_VHT_PARAMETER_INSERT } @@ -1265,6 +1282,36 @@ impl IgvmDirectiveHeader { variable_headers, ); } + IgvmDirectiveHeader::AArch64CcaVpContext { + compatibility_mask, + vp_index, + context, + } => { + // Pad file data to 4K. + let align_up_iter = + std::iter::repeat_n(&0u8, PAGE_SIZE_4K as usize - context.as_bytes().len()); + let data: Vec = context + .as_bytes() + .iter() + .chain(align_up_iter) + .copied() + .collect(); + let file_offset = file_data.write_file_data(&data); + + let info = IGVM_VHS_VP_CONTEXT { + gpa: 0.into(), + compatibility_mask: *compatibility_mask, + file_offset, + vp_index: *vp_index, + reserved: 0, + }; + + append_header( + &info, + IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT, + variable_headers, + ); + } IgvmDirectiveHeader::X64VbsVpContext { vtl, registers, @@ -1460,6 +1507,9 @@ impl IgvmDirectiveHeader { AArch64VbsVpContext { compatibility_mask, .. } => Some(*compatibility_mask), + AArch64CcaVpContext { + compatibility_mask, .. + } => Some(*compatibility_mask), ParameterInsert(info) => Some(info.compatibility_mask), ErrorRange { compatibility_mask, .. @@ -1508,6 +1558,9 @@ impl IgvmDirectiveHeader { AArch64VbsVpContext { compatibility_mask, .. } => Some(compatibility_mask), + AArch64CcaVpContext { + compatibility_mask, .. + } => Some(compatibility_mask), ParameterInsert(info) => Some(&mut info.compatibility_mask), ErrorRange { compatibility_mask, .. @@ -1664,6 +1717,11 @@ impl IgvmDirectiveHeader { registers: _, compatibility_mask: _, } => {} + IgvmDirectiveHeader::AArch64CcaVpContext { + compatibility_mask: _, + vp_index: _, + context: _, + } => {} IgvmDirectiveHeader::ParameterInsert(param) => { if param.gpa % PAGE_SIZE_4K != 0 { return Err(BinaryHeaderError::UnalignedAddress(param.gpa)); @@ -1905,6 +1963,35 @@ impl IgvmDirectiveHeader { context, } } + Some(IgvmPlatformType::CCA) => { + // Read the context which is stored as 4K file data. + let start = (header.file_offset - file_data_start) as usize; + if file_data.len() < start { + return Err(BinaryHeaderError::InvalidDataSize); + } + + let data = file_data + .get(start..) + .and_then(|x| x.get(..PAGE_SIZE_4K as usize)) + .ok_or(BinaryHeaderError::InvalidDataSize)?; + + // Copy the context bytes into the context structure, + // and validate the remaining bytes are 0. + // todo: zerocopy: as of 0.8, can recover from allocation failure + let mut context = IgvmVpContextAArch64Cca::new_box_zeroed().unwrap(); + let (context_slice, remaining) = + data.split_at(size_of::()); + context.as_mut_bytes().copy_from_slice(context_slice); + if remaining.iter().any(|b| *b != 0) { + return Err(BinaryHeaderError::InvalidContext); + } + + IgvmDirectiveHeader::AArch64CcaVpContext { + compatibility_mask: header.compatibility_mask, + vp_index: header.vp_index, + context, + } + } _ => { // Unsupported compatibility mask or isolation type return Err(BinaryHeaderError::InvalidVpContextPlatformType); @@ -2374,6 +2461,14 @@ impl IgvmFile { }); } } + IgvmPlatformType::CCA => { + if revision.arch() != Arch::AArch64 { + return Err(Error::PlatformArchUnsupported { + arch: revision.arch(), + platform: info.platform_type, + }); + } + } _ => return Err(Error::InvalidPlatformType), } @@ -2629,6 +2724,18 @@ impl IgvmFile { && ident.vtl == *vtl) }) } + IgvmDirectiveHeader::AArch64CcaVpContext { + compatibility_mask: _, + context: _, + vp_index: _, + } => { + if revision.arch() != Arch::AArch64 { + return Err(Error::InvalidHeaderArch { + arch: revision.arch(), + header_type: "AArch64CcaVpContext".into(), + }); + } + } IgvmDirectiveHeader::ParameterInsert(info) => { match parameter_areas.get_mut(&info.parameter_area_index) { Some(state) if *state == ParameterAreaState::Allocated => { @@ -3350,7 +3457,8 @@ impl IgvmFile { | SnpIdBlock { .. } | VbsMeasurement { .. } | X64VbsVpContext { .. } - | AArch64VbsVpContext { .. } => {} + | AArch64VbsVpContext { .. } + | AArch64CcaVpContext { .. } => {} ParameterArea { parameter_area_index, .. @@ -3463,6 +3571,13 @@ mod tests { }) } + fn new_guest_policy(policy: u64, compatibility_mask: u32) -> IgvmInitializationHeader { + IgvmInitializationHeader::GuestPolicy { + policy, + compatibility_mask, + } + } + fn new_page_data(page: u64, compatibility_mask: u32, data: &[u8]) -> IgvmDirectiveHeader { IgvmDirectiveHeader::PageData { gpa: page * PAGE_SIZE_4K, @@ -3593,7 +3708,7 @@ mod tests { } #[test] - fn test_basic_v2_aarch64() { + fn test_basic_v2_aarch64_vbs() { let data1 = vec![1; PAGE_SIZE_4K as usize]; let data2 = vec![2; PAGE_SIZE_4K as usize]; let data3 = vec![3; PAGE_SIZE_4K as usize]; @@ -3631,6 +3746,47 @@ mod tests { assert_igvm_equal(&file, &deserialized_binary_file); } + #[test] + fn test_basic_v2_aarch64_cca() { + let data1 = vec![1; PAGE_SIZE_4K as usize]; + let data2 = vec![2; PAGE_SIZE_4K as usize]; + let data3 = vec![3; PAGE_SIZE_4K as usize]; + let data4 = vec![4; PAGE_SIZE_4K as usize]; + let context = IgvmVpContextAArch64Cca::new_box_zeroed().unwrap(); + let file = IgvmFile { + revision: IgvmRevision::V2 { + arch: Arch::AArch64, + page_size: PAGE_SIZE_4K as u32, + }, + platform_headers: vec![new_platform(0x1, IgvmPlatformType::CCA)], + initialization_headers: vec![new_guest_policy((CcaHashAlgorithm::SHA512.0 as u64) << 1 + | (CcaLfaPolicy::LFA_ALLOW.0 as u64) << 9, 0x1)], + directive_headers: vec![ + new_page_data(0, 1, &data1), + new_page_data(1, 1, &data2), + new_page_data(2, 1, &data3), + new_page_data(4, 1, &data4), + new_page_data(10, 1, &data1), + new_page_data(11, 1, &data2), + new_page_data(12, 1, &data3), + new_page_data(14, 1, &data4), + new_parameter_area(0), + new_parameter_usage(0), + new_parameter_insert(20, 0, 1), + IgvmDirectiveHeader::AArch64CcaVpContext { + compatibility_mask: 0x1, + vp_index: 0xabcd, + context, + }, + ], + }; + let mut binary_file = Vec::new(); + file.serialize(&mut binary_file).unwrap(); + + let deserialized_binary_file = IgvmFile::new_from_binary(&binary_file, None).unwrap(); + assert_igvm_equal(&file, &deserialized_binary_file); + } + // test platform filter works correctly // test state transition checks enforce correct ordering } diff --git a/igvm/src/registers.rs b/igvm/src/registers.rs index ea247a7..0f2e346 100644 --- a/igvm/src/registers.rs +++ b/igvm/src/registers.rs @@ -178,11 +178,18 @@ impl X86Register { } /// AArch64 registers that can be stored in IGVM VP context structures. +/// The list can be extended if new registers are needed by new context. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AArch64Register { Pc(u64), X0(u64), X1(u64), + X2(u64), + X3(u64), + X4(u64), + X5(u64), + X6(u64), + X7(u64), Cpsr(u64), SctlrEl1(u64), TcrEl1(u64), @@ -198,6 +205,12 @@ impl AArch64Register { AArch64Register::Pc(reg) => (HvArm64RegisterName::XPc, reg.into()), AArch64Register::X0(reg) => (HvArm64RegisterName::X0, reg.into()), AArch64Register::X1(reg) => (HvArm64RegisterName::X1, reg.into()), + AArch64Register::X2(reg) => (HvArm64RegisterName::X2, reg.into()), + AArch64Register::X3(reg) => (HvArm64RegisterName::X3, reg.into()), + AArch64Register::X4(reg) => (HvArm64RegisterName::X4, reg.into()), + AArch64Register::X5(reg) => (HvArm64RegisterName::X5, reg.into()), + AArch64Register::X6(reg) => (HvArm64RegisterName::X6, reg.into()), + AArch64Register::X7(reg) => (HvArm64RegisterName::X7, reg.into()), AArch64Register::Cpsr(reg) => (HvArm64RegisterName::Cpsr, reg.into()), AArch64Register::SctlrEl1(reg) => (HvArm64RegisterName::SctlrEl1, reg.into()), AArch64Register::TcrEl1(reg) => (HvArm64RegisterName::TcrEl1, reg.into()), @@ -287,6 +300,12 @@ impl TryFrom for AArch64Register { HvArm64RegisterName::XPc => Self::Pc(register_value.as_u64()), HvArm64RegisterName::X0 => Self::X0(register_value.as_u64()), HvArm64RegisterName::X1 => Self::X1(register_value.as_u64()), + HvArm64RegisterName::X2 => Self::X1(register_value.as_u64()), + HvArm64RegisterName::X3 => Self::X1(register_value.as_u64()), + HvArm64RegisterName::X4 => Self::X1(register_value.as_u64()), + HvArm64RegisterName::X5 => Self::X1(register_value.as_u64()), + HvArm64RegisterName::X6 => Self::X1(register_value.as_u64()), + HvArm64RegisterName::X7 => Self::X1(register_value.as_u64()), HvArm64RegisterName::Cpsr => Self::Cpsr(register_value.as_u64()), HvArm64RegisterName::SctlrEl1 => Self::SctlrEl1(register_value.as_u64()), HvArm64RegisterName::TcrEl1 => Self::TcrEl1(register_value.as_u64()), diff --git a/igvm_defs/src/lib.rs b/igvm_defs/src/lib.rs index 7d11fd2..048e25a 100644 --- a/igvm_defs/src/lib.rs +++ b/igvm_defs/src/lib.rs @@ -386,6 +386,10 @@ pub enum IgvmPlatformType { #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] SEV_ES = 0x05, + /// Arm CCA + #[cfg(feature = "unstable")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] + CCA = 0x06, } impl Default for IgvmPlatformType { @@ -410,6 +414,10 @@ pub const IGVM_SEV_PLATFORM_VERSION: u16 = 0x1; #[cfg(feature = "unstable")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] pub const IGVM_SEV_ES_PLATFORM_VERSION: u16 = 0x1; +/// Platform version for [`IgvmPlatformType::Cca`]. +#[cfg(feature = "unstable")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] +pub const IGVM_CCA_PLATFORM_VERSION: u16 = 0x1; /// This structure indicates which isolation platforms are compatible with this /// guest image. A separate [`IGVM_VHS_SUPPORTED_PLATFORM`] structure must be @@ -451,6 +459,8 @@ pub struct IGVM_VHS_GUEST_POLICY { /// For AMD SEV-SNP, this is [`SnpPolicy`]. /// /// For Intel TDX, this is [`TdxPolicy`]. + /// + /// For Arm CCA, this is [`CcaPolicy`]. pub policy: u64, /// Compatibility mask. pub compatibility_mask: u32, @@ -498,6 +508,74 @@ pub struct TdxPolicy { pub reserved: u64, } +/// Hash algorithms for Arm CCA used in [`CcaPolicy::hash_algorithm`]. +/// These algorithms correspond to those defined in the RMM specification, +/// section "C2.24 RmmHashAlgorithm type". +#[open_enum] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum CcaHashAlgorithm { + /// SHA-256 hash algorithm + SHA256 = 0x0, + /// SHA-384 hash algorithm + SHA384 = 0x1, + /// SHA-512 hash algorithm + SHA512 = 0x2, +} + +impl CcaHashAlgorithm { + const fn from_bits(bits: u8) -> Self { + Self(bits) + } + + const fn into_bits(self) -> u8 { + self.0 + } +} + +/// Live Firmware Activation (LFA) policies for Arm CCA used in [`CcaPolicy::lfa_policy`]. +#[open_enum] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum CcaLfaPolicy { + /// Components within the Realm's TCB cannot be updated via LFA while + /// the realm is running. + LFA_DISALLOW = 0x0, + /// Components within the Realm's TCB can be updated via LFA while + /// the realm is running. + LFA_ALLOW = 0x1, +} + +impl CcaLfaPolicy { + const fn from_bits(bits: u8) -> Self { + Self(bits) + } + + const fn into_bits(self) -> u8 { + self.0 + } +} + +/// The Arm CCA policy used in [`IGVM_VHS_GUEST_POLICY::policy`]. +#[bitfield(u64)] +#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] +pub struct CcaPolicy { + /// Whether debug is allowed for the Realm. + #[bits(1)] + pub debug_allowed: u8, + /// Hash algorithm to measure the initial state of the Realm. + #[bits(8)] + pub hash_algorithm: CcaHashAlgorithm, + /// Live Firmware Activation (LFA) policy for the components within the Realm's TCB. + #[bits(2)] + pub lfa_policy: CcaLfaPolicy, + /// Whether the Memory Encryption Context (MEC) is shared. + #[bits(1)] + pub mec_shared: u8, + #[bits(52)] + pub reserved: u64, +} + /// This region describes VTL2. pub const IGVM_VHF_RELOCATABLE_REGION_IS_VTL2: u8 = 0x1; /// The starting executable address for the specified VP and VTL should be @@ -976,6 +1054,9 @@ pub struct IgvmNativeVpContextX64 { /// /// The format consists of a [`VbsVpContextHeader`] followed by a /// `register_count` of [`VbsVpContextRegister`]. +/// +/// NOTE: we reuse [`VbsVpContextHeader`] and [`VbsVpContextHeader`] for ARM64 +/// CCA and `vtl` means `plane` when used for CCA. #[repr(C)] #[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)] pub struct VbsVpContextHeader { @@ -1000,6 +1081,47 @@ pub struct VbsVpContextRegister { const_assert_eq!(size_of::(), 0x20); +/// Format of [`IGVM_VHS_VP_CONTEXT`] file data for a native ARM64 CCA image. +/// +/// The VP Context corresponds to the REC (Realm Execution Context) in CCA. +/// Therefore, the fields listed below match those defined in the RMM +/// specification, section "B4.6.69 RmiRecParams type". +/// +/// These include the general-purpose registers x0–x7, the PC register, +/// and a 64-bit flags field. All of these fields contribute to the +/// RIM (Realm Initial Measurement). +/// +/// One exception is `mpidr`, which is not included here because it is +/// determined by the host and does not need to be specified in the +/// IGVM file. +/// +/// Any registers not explicitly specified here are initialized to their +/// architectural reset values. +#[repr(C)] +#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] +pub struct IgvmVpContextAArch64Cca { + /// X0 register. + pub x0: u64, + /// X1 register. + pub x1: u64, + /// X2 register. + pub x2: u64, + /// X3 register. + pub x3: u64, + /// X4 register. + pub x4: u64, + /// X5 register. + pub x5: u64, + /// X6 register. + pub x6: u64, + /// X7 register. + pub x7: u64, + /// Program counter. + pub pc: u64, + /// Flags for the context. + pub flags: u64, +} + /// This structure describes memory the IGVM file expects to be present in the /// guest. This is a hint to the loader that the guest will not function without /// memory present at the specified range, and should terminate the load process