use crate::{
error::*,
launch::snp::PageType,
measurement::{
gctx::{Gctx, Updating, VMSA_GPA},
ovmf::{OvmfSevMetadataSectionDesc, SectionType, OVMF},
sev_hashes::SevHashes,
vcpu_types::CpuType,
vmsa::{GuestFeatures, VMMType, VMSA},
},
parser::{ByteParser, Decoder, Encoder},
util::parser_helper::{ReadExt, WriteExt},
};
use hex::FromHex;
use std::{
convert::{TryFrom, TryInto},
io::{Read, Write},
};
use std::{fmt, path::PathBuf};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_big_array::BigArray;
const _PAGE_MASK: u64 = 0xfff;
pub(crate) const LD_BITS: usize = 384;
pub(crate) const LD_BYTES: usize = LD_BITS / 8;
#[repr(C)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct SnpLaunchDigest(#[cfg_attr(feature = "serde", serde(with = "BigArray"))] [u8; LD_BYTES]);
impl Default for SnpLaunchDigest {
fn default() -> Self {
Self([0u8; LD_BYTES])
}
}
impl TryFrom<&[u8]> for SnpLaunchDigest {
type Error = MeasurementError;
fn try_from(bytes: &[u8]) -> Result<Self, MeasurementError> {
Ok(SnpLaunchDigest(bytes.try_into()?))
}
}
impl TryInto<Vec<u8>> for SnpLaunchDigest {
type Error = MeasurementError;
fn try_into(self) -> Result<Vec<u8>, MeasurementError> {
Ok((self.0).to_vec())
}
}
impl Encoder<()> for SnpLaunchDigest {
fn encode(&self, writer: &mut impl Write, _: ()) -> Result<(), std::io::Error> {
writer.write_bytes(self.0, ())?;
Ok(())
}
}
impl Decoder<()> for SnpLaunchDigest {
fn decode(reader: &mut impl Read, _: ()) -> Result<Self, std::io::Error> {
let ld = reader.read_bytes()?;
Ok(Self(ld))
}
}
impl ByteParser<()> for SnpLaunchDigest {
type Bytes = [u8; LD_BYTES];
const EXPECTED_LEN: Option<usize> = Some(LD_BYTES);
}
impl fmt::LowerHex for SnpLaunchDigest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in &self.0 {
write!(f, "{:02x}", b)?;
}
Ok(())
}
}
impl SnpLaunchDigest {
pub fn new(data: [u8; LD_BYTES]) -> Self {
Self(data)
}
pub fn get_hex_ld(self) -> String {
format!("{:x}", self)
}
}
fn snp_update_kernel_hashes(
gctx: &mut Gctx<Updating>,
ovmf: &OVMF,
sev_hashes: Option<&SevHashes>,
gpa: u64,
size: usize,
) -> Result<(), MeasurementError> {
match sev_hashes {
Some(hash) => {
let sev_hashes_table_gpa = ovmf.sev_hashes_table_gpa()?;
let page_offset = sev_hashes_table_gpa & _PAGE_MASK;
let sev_hashes_page = hash.construct_page(page_offset as usize)?;
assert_eq!(sev_hashes_page.len(), size);
gctx.update_page(
PageType::Normal,
gpa,
Some(sev_hashes_page.as_slice()),
None,
)?
}
_ => gctx.update_page(PageType::Zero, gpa, None, Some(size))?,
}
Ok(())
}
fn snp_update_section(
desc: &OvmfSevMetadataSectionDesc,
gctx: &mut Gctx<Updating>,
ovmf: &OVMF,
sev_hashes: Option<&SevHashes>,
vmm_type: VMMType,
) -> Result<(), MeasurementError> {
match desc.section_type {
SectionType::SnpSecMemory => gctx.update_page(
PageType::Zero,
desc.gpa.into(),
None,
Some(desc.size as usize),
)?,
SectionType::SnpSecrets => {
gctx.update_page(PageType::Secrets, desc.gpa.into(), None, None)?
}
SectionType::Cpuid => {
if vmm_type != VMMType::EC2 {
gctx.update_page(PageType::Cpuid, desc.gpa.into(), None, None)?
}
}
SectionType::SnpKernelHashes => {
snp_update_kernel_hashes(gctx, ovmf, sev_hashes, desc.gpa.into(), desc.size as usize)?
}
SectionType::SvsmCaa => gctx.update_page(
PageType::Zero,
desc.gpa.into(),
None,
Some(desc.size as usize),
)?,
}
Ok(())
}
fn snp_update_metadata_pages(
gctx: &mut Gctx<Updating>,
ovmf: &OVMF,
sev_hashes: Option<&SevHashes>,
vmm_type: VMMType,
) -> Result<(), MeasurementError> {
for desc in ovmf.metadata_items().iter() {
snp_update_section(desc, gctx, ovmf, sev_hashes, vmm_type)?;
}
if vmm_type == VMMType::EC2 {
for desc in ovmf.metadata_items() {
if desc.section_type == SectionType::Cpuid {
gctx.update_page(PageType::Cpuid, desc.gpa.into(), None, None)?
}
}
}
if sev_hashes.is_some() && !ovmf.has_metadata_section(SectionType::SnpKernelHashes) {
return Err(MeasurementError::MissingSection(
"SNP_KERNEL_HASHES".to_string(),
));
};
Ok(())
}
pub fn calc_snp_ovmf_hash(ovmf_file: PathBuf) -> Result<SnpLaunchDigest, MeasurementError> {
let ovmf = OVMF::new(ovmf_file)?;
let mut gctx = Gctx::default();
gctx.update_page(PageType::Normal, ovmf.gpa(), Some(ovmf.data()), None)?;
let gctx = gctx.finished();
Ok(gctx.ld())
}
pub struct SnpMeasurementArgs<'a> {
pub vcpus: u32,
pub vcpu_type: CpuType,
pub ovmf_file: PathBuf,
pub guest_features: GuestFeatures,
pub kernel_file: Option<PathBuf>,
pub initrd_file: Option<PathBuf>,
pub append: Option<&'a str>,
pub ovmf_hash_str: Option<&'a str>,
pub vmm_type: Option<VMMType>,
}
pub fn snp_calc_launch_digest(
snp_measurement: SnpMeasurementArgs,
) -> Result<SnpLaunchDigest, MeasurementError> {
let ovmf = OVMF::new(snp_measurement.ovmf_file)?;
let mut gctx: Gctx<Updating> = match snp_measurement.ovmf_hash_str {
Some(hash) => {
let ovmf_hash = Vec::from_hex(hash)?;
Gctx::new(ovmf_hash.as_slice())?
}
_ => {
let mut gctx = Gctx::default();
gctx.update_page(PageType::Normal, ovmf.gpa(), Some(ovmf.data()), None)?;
gctx
}
};
let sev_hashes = match snp_measurement.kernel_file {
Some(kernel) => Some(SevHashes::new(
kernel,
snp_measurement.initrd_file,
snp_measurement.append,
)?),
_ => None,
};
let official_vmm_type = match snp_measurement.vmm_type {
Some(vmm) => vmm,
_ => VMMType::QEMU,
};
snp_update_metadata_pages(&mut gctx, &ovmf, sev_hashes.as_ref(), official_vmm_type)?;
let vmsa = VMSA::new(
ovmf.sev_es_reset_eip()?.into(),
snp_measurement.vcpu_type,
official_vmm_type,
Some(snp_measurement.vcpus as u64),
snp_measurement.guest_features,
);
for vmsa_page in vmsa.pages(snp_measurement.vcpus as usize)?.iter() {
gctx.update_page(PageType::Vmsa, VMSA_GPA, Some(vmsa_page.as_slice()), None)?
}
let gctx = gctx.finished();
Ok(gctx.ld())
}