use super::MeasurementError;
use super::SHA_384_OUTPUT_SIZE;
use crate::IgvmDirectiveHeader;
use crate::IgvmInitializationHeader;
use igvm_defs::IgvmPageDataType;
use igvm_defs::PAGE_SIZE_4K;
use sha2::Digest;
use sha2::Sha384;
use std::collections::HashMap;
use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;
const PAGE_SIZE_4K_USIZE: usize = PAGE_SIZE_4K as usize;
mod snp_page_type {
pub const NORMAL: u8 = 0x1;
pub const VMSA: u8 = 0x2;
pub const UNMEASURED: u8 = 0x4;
pub const SECRETS: u8 = 0x5;
pub const CPUID: u8 = 0x6;
}
#[repr(C)]
#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
struct SnpPageInfo {
digest_current: [u8; 48],
contents: [u8; 48],
length: u16,
page_type: u8,
imi_page_bit: u8,
lower_vmpl_permissions: u32,
gpa: u64,
}
pub fn generate_snp_measurement(
initialization_headers: &[IgvmInitializationHeader],
directive_headers: &[IgvmDirectiveHeader],
compatibility_mask: u32,
) -> Result<[u8; SHA_384_OUTPUT_SIZE], MeasurementError> {
let mut parameter_area_table = HashMap::new();
let mut launch_digest = [0u8; SHA_384_OUTPUT_SIZE];
let zero_page = [0u8; PAGE_SIZE_4K_USIZE];
let zero_digest: [u8; SHA_384_OUTPUT_SIZE] = {
let mut h = Sha384::new();
h.update(zero_page);
h.finalize().into()
};
let mut padding_vec = vec![0u8; PAGE_SIZE_4K_USIZE];
let mut measure_page =
|page_type: u8, gpa: u64, page_data: Option<&[u8]>| -> Result<(), MeasurementError> {
let hash_contents: [u8; SHA_384_OUTPUT_SIZE] = match page_data {
Some(data) => match data.len() {
0 => zero_digest,
len if len < PAGE_SIZE_4K_USIZE => {
padding_vec.fill(0);
padding_vec[..len].copy_from_slice(data);
let mut h = Sha384::new();
h.update(&padding_vec);
h.finalize().into()
}
PAGE_SIZE_4K_USIZE => {
let mut h = Sha384::new();
h.update(data);
h.finalize().into()
}
len => return Err(MeasurementError::UnsupportedPageSize(len)),
},
None => [0u8; SHA_384_OUTPUT_SIZE],
};
let info = SnpPageInfo {
digest_current: launch_digest,
contents: hash_contents,
length: size_of::<SnpPageInfo>() as u16,
page_type,
imi_page_bit: 0,
lower_vmpl_permissions: 0,
gpa,
};
let mut h = Sha384::new();
h.update(info.as_bytes());
launch_digest = h.finalize().into();
Ok(())
};
let _policy = initialization_headers
.iter()
.find_map(|h| {
if let IgvmInitializationHeader::GuestPolicy {
policy,
compatibility_mask: mask,
} = h
{
if mask & compatibility_mask == compatibility_mask {
return Some(*policy);
}
}
None
})
.ok_or(MeasurementError::MissingGuestPolicy)?;
for header in directive_headers {
if header
.compatibility_mask()
.map(|mask| mask & compatibility_mask != compatibility_mask)
.unwrap_or(false)
{
continue;
}
match header {
IgvmDirectiveHeader::ParameterArea {
number_of_bytes,
parameter_area_index,
initial_data: _,
} => {
parameter_area_table.insert(*parameter_area_index, *number_of_bytes);
}
IgvmDirectiveHeader::PageData {
gpa,
flags,
data_type,
data,
..
} => {
if flags.shared() {
continue;
}
let (page_type, data) = match *data_type {
IgvmPageDataType::SECRETS => (snp_page_type::SECRETS, None),
IgvmPageDataType::CPUID_DATA | IgvmPageDataType::CPUID_XF => {
(snp_page_type::CPUID, None)
}
_ => {
if flags.unmeasured() {
(snp_page_type::UNMEASURED, None)
} else {
(snp_page_type::NORMAL, Some(data.as_slice()))
}
}
};
measure_page(page_type, *gpa, data)?;
}
IgvmDirectiveHeader::ParameterInsert(param) => {
let parameter_area_size = parameter_area_table
.get(¶m.parameter_area_index)
.ok_or(MeasurementError::InvalidParameterAreaIndex(
param.parameter_area_index,
))?;
for gpa in (param.gpa..param.gpa + *parameter_area_size).step_by(PAGE_SIZE_4K_USIZE)
{
measure_page(snp_page_type::UNMEASURED, gpa, None)?;
}
}
IgvmDirectiveHeader::SnpVpContext { gpa, vmsa, .. } => {
let vmsa_bytes = vmsa.as_ref().as_bytes();
measure_page(snp_page_type::VMSA, *gpa, Some(vmsa_bytes))?;
}
_ => {}
}
}
Ok(launch_digest)
}